Building both accurate and performant factory data in our test suites is challenging. It is common that as we push our factory definitions to create data in the same way as in our real-world production environments, the amount of inserted data skyrockets. As your model matures, as you add more associations that have children with yet more children, all of the sudden you have an innocent looking factory that creates a massive object tree. And your suite uses it many times, and your build time suffers.

While this is all going on, Rails 5 introduces a change where belongs_to associations must be present, unless passed optional: true. This breaks passing fake IDs as primary keys to constructors. This no longer blends:

thing = described_class.create!(:thing, user_id: 1, owner_id: 2)
other_thing = described_class.new(:thing, user_id: 1, owner_id: 2)

This cuts out all extraneous object creation, and works fine for simple tests. Despite not strictly reflecting real-world data, it is an order of magnitude faster -- 0.2 seconds without actual objects, and 2 seconds with.

We want to keep tests like this intact despite the first object failing validation, since its User and Owner associations must be present. With FactoryBot, simply pass them as full mocks with a fake ID instead:

thing = described_class.create!(:thing, user: build_stubbed(:user, id: 1),
                                        owner: build_stubbed(:owner, id: 2))

other_thing = described_class.new(:thing, user: build_stubbed(:user, id: 1),
                                          owner: build_stubbed(:owner, id: 2))

The associations now hold objects, which pass the new belongs_to validations, and we continue to avoid persisting actual data, keeping our performance tight.

More blog posts