factory_girl

This week, Chris is joined by thoughtbot's Development Director in Boston, Josh Clayton, to talk about factory_girl; a topic near and dear to Josh's heart, as he's been the maintainer of the project for over five years.

Factory Girl is one of thoughtbot's most popular open-source projects, and is one of the few gems that we consider a requirement for every project, new or old. Let's discover why.


This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/factory-girl

In case anyone is wondering how you would also include both a dynamic variable and a sequence into an attribute, it’d work something like this…

factory :user do
  name "dan"
  sequence(:email) { |n| "#{n}#{name}@gmail.com" }
end

The n is the part that sequences, and #{name} will include the name.

@christoomey Another incredibly useful video.

Do you guys recommend any sort of file structure for factories? Or do you toss everything in spec/factories/factories.rb?

I also have a question on creating associations with call backs…(and maybe this one is outside the scope of comments on Upcase)… Let’s say I have a user that has_one :account. When creating the user and account association, do you prefer the after(:stub) callback? of the after(:build) callback?

FactoryGirl.define do
  factory :user do
    email "d@gmail.com"

    factory :user_with_account do
       after(:build) do |user|
         FactoryGirl.build(:account, user: user)
       end
    
    # OR
       after(:stub) do |user|
         FactoryGirl.build(:account, user: user)
       end
     end
   end
end

I know you’re a big fan of build_stubbed which would use the after(:stub) callback. Thoughts?

@thedanotto the current best practice recommended at thoughtbot is to put all your factories into spec/factories.rb. There’s some other nice guidelines there for how to organize attributes, traits, associations, etc. in your factories.

I’ve worked on projects where the factories were broken up by model class, which is fine as well (I personally prefer separate classes, but if you have any STI or subclassing the “all factories in one file” approach is certainly easier to search).

I think I would only create associations in an after(:create) callback. I’m wary of checking for associations in a situation where the model under test hasn’t been saved to the database. If you’re limiting your use of FactoryGirl.create to scenarios that require the database, then that use will be a signal that your test is database dependent even on a quick reading.

@geoffharcourt Thanks. That’s really helpful!

1 Like

@christoomey
Thanks for the video, I learned a lot!

My $0.02 of feedback: I’d have appreciated the video more if Josh as the maintainer would have had more opportunity to talk and if the video would have dived more into the nuances of FG. More code examples would have been nice as well.

Also, showing the code examples a bit longer before switching back would also work in your favor imho. The audio would sometimes be just fine while spending more time with the code examples—that way I wouldn’t need to pause the video for that most of the time :grin:

@christoomey thanks for the video- seeing how you can play around with it in the console was really valuable. Using the console and trying things out is what helped me get good enough at Factory Girl to start using it in real life.

@christoomey @joshclayton Great video guys; I have some thoughts related to the way we normally use factory girls in general.

You guys mention the mystery guest anti pattern and I agree that Rails fixtures can lead to this problem; but I feel factory_girl leads to another different issue and is the fact that we are mutating the state of the database to make assertion against it. If we are testing a sub system of our application shouldn’t there be an entry point in the production code base that put the system in that state and then we assert on the side effects? Factory girl is meant to be use from the test but I argue that it should be use exclusively to test active record models and not to generate dynamic data to test other parts of the system that could lead to hide concepts from the domain because we have “another way” of putting the system in a certain state.

Let me know what you think about this; thanks in advance,

@cored you bring up a valid point, and one I think every developer struggles with. By creating literally all data through the UI, assuming it is well-abstracted to helper methods/page objects to decouple tests, the problem then becomes test suite times.

For example, by testing ownership of todos within an application, you’d have to write a test signing in one person, have them create a todo, sign out, sign in as someone else, and ensure they can’t see the todo for the other person. In contrast, you could create a todo (without being specific about the owner), sign into a new account, and assert the todo isn’t present.

There are definitely cases where it can be difficult to model state in factory girl because of the complexity. In those situations, it’s difficult to provide a blanket answer, but if you’re diligent about factory maintenance, that often works. Alternatively, you could create a baseline with FG and drive parts of the internal state changing through the UI to help guarantee the correct state in a flow.

@joshuaclayton I see what you mean; but that would be a pain if we keep testing the entire stack. I think it will be easier to just call the service layer which to me in a system represent the entry point for an use case and put the system under test in that same context. The advantage of this is that if we change something it will fail for the right reasons at the test level. With factory girl since we are mutating the state of the data not exactly the state of the system as a whole it will create an illusion that the data is actually the application business logic instead of thinking about it as where we save things. Right now I’m using factory girl when I do test exclusively for the persistence logic; I try to create concepts for every entry point in the system so I can use then without calling internal implementation details as for example the way we store first_name and password. I talked to much I hope it’s more clear the point I want to pass across.

Thanks again for your time,