Nested factories in Factory Bot: what they are and how to use them

by Jason Swett,

Sometimes you want to be able to create records that are 95% the same as the “default” but have one or two small differences.

Physician user example

Let’s use the following as an example. Let’s say that in the majority of your application’s tests that involve a User, you want just a regular old user. Maybe you have 30 such tests.

But for a handful of tests you want to use a special kind of user, a user that indicates that the person using the application is a physician.

In this scenario, the technical difference between a “physician user” and a regular user is that a physician user has a role attribute that’s set to 'physician'. Let’s say there are 6 tests that use a physician user.

Options for addressing this challenge

You have the following options for addressing this challenge.

First, you could set the role to “physician” individually on each of your 6 physician user tests. Unfortunately this would be bad for all the reasons that duplication is bad. If the nature of your physician user were ever to change, you’d have to know that you’d have to make the change in all 6 of these places. This solution isn’t great.

Alternatively, you could alter the user factory to always have role set to “physician”. Under this scenario, every user would always be a physician user, even when it’s not necessary. Even if this would function properly from a technical perspective, it would be misleading to any maintainers of the test suite. For any test, it’s a good idea to create the minimum amount of setup data and no more. An outside observer has no way of knowing which parts of a test’s setup data are necessary and which aren’t, and so must assume it’s all potentially necessary. We wouldn’t want to send our future maintainers a false message.

A third option is to create an alternate version of the user factory that inherits from the original, and use that version in our physician tests. This is the right answer. In this scenario we’re not duplicating our setup steps, and we’re also not modifying our “default” factory. The name for this in Factory Bot is nested factories.

How nested factories work

Consider the following User factory.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }
  end
end

In addition to the above, we can define a nested factory called :physician_user which will have all the same attributes as our regular users, but with the extra attribute of role with the value of 'physician'.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }

    factory :physician_user do
      role { 'physician' }
    end
  end
end

To create a physician user we would simply invoke the following:

FactoryBot.create(:physician_user)

Unlike root-level factory names which map to Active Record models, nested factory names are fully arbitrary.

Takeaways

  • If you have setup data needs that are common across tests, it’s usually not a good idea to duplicate the setup. Duplication in test code usually has the same negative consequences as duplication in application code.
  • Each test should have as much setup data as is necessary for that test and no more. Otherwise a maintainer will have a hard time telling what’s necessary and what’s not.
  • Factory Bot’s nested factories can help us keep our test code DRY while continuing to adhere to certain good testing principles.

6 thoughts on “Nested factories in Factory Bot: what they are and how to use them

  1. Francisco Quintero

    Why not use traits? I’ve always defaulted to use traits for this kind of scenario where an specific attribute needs to be different.

    Then, I’ll group several traits and have a very customized nested factory.

    Reply
    1. Jason Swett Post author

      Good question. I might do a separate post on when I use traits vs. nested factories. I use both but I haven’t yet articulated to myself why I use one vs. the other in any given situation.

      Reply
    2. Phil Pirozhkov

      Traits that are used solely in the factory definition itself are useful in two cases:
      – when several (but not all) nested factories share common attributes
      – when the base model is abstract because FB doesn’t have a concept of an abstract factory, so a trait is used as a factory for that base abstract model. That way, `FactoryBot.lint` doesn’t complain that it couldn’t instantiate that base model

      Reply
  2. Leonardo Xavier de Brito

    The main benefit of Traits is that they are composable, so you can chain several traits to the factory.
    I also avoid using nested factories because of naming. In large systems, or you risk a name collision, or you’ll have poor name expressiveness, or long names hard to memorize.

    Reply
    1. Jason Swett Post author

      I use traits when the object “has” something and nested factories when the object “is” something. If I’m using nested factories, chaining isn’t desirable/sensical because something never “is” more than one thing at once.

      Regarding the name collision issue, I’ll take your word for it, although I’ve never had that problem myself, perhaps partly because I use namespaces.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *