Factories for test objects. Use them.

by Justin Blake on February 23, 2009

This is for those of you who have moved past fixtures in your rails tests, but haven’t yet discovered factories. If you are creating test objects manually in your tests, this is for you. If you are creating test objects using a bit more abstraction, you may move on. If you are still using fixtures, there is no hope for you. Become a forest ranger.

Go on.

Whoever is left, you are in a transition period. You are probably writing tests that look like this:

should "return only buttered biscuits" do
  yummy = Biscuit.create!(:buttered => true)
  gross = Biscuit.create!(:buttered => false)
  assert_equal [yummy], Biscuit.buttered
end

Not bad, but now you want to add a required attribute to Biscuit. Since you are Agile Coder Extraordinaire, you write the test first:

# You're using Shoulda here, hence the brevity:
should_require_attributes :flavor

Failed! Good. You now make it green by adding the attribute and validation. That test is passing, but wait… now you’re seeing red again! Oh no!

1) Error:
test: Biscuit should return only buttered biscuits. (BiscuitTest):
ActiveRecord::RecordInvalid: Validation failed: Flavor can't be blank

Looks like you have to go add the :flavor attribute to the create calls in that first test. Might not seem like a big deal, but imagine if you had a much larger test suite and creates like that were sprinkled throughout. You’d be fixing failing tests all day that have nothing to do with the behavior you’re actually changing. Yuck.

You need to abstract the creation of your test objects. That’s where factories come in. A factory is basically an object with methods for creating other objects. This lets you set up a baseline set of default attributes in one place instead of all over the place.

This could be as simple as a helper method in test_helper.rb. Just remember to allow overriding your default attributes:

# In test_helper.rb
def create_biscuit(attributes = {})
  Biscuit.create!({
    :flavor => "Homestyle"
  }.merge(attributes))
end

Again, you could do it this way, but since you are smart, you decide not to reinvent the wheel, and use factory_girl instead:

# In factories.rb
Factory(:biscuit) do |b|
  b.flavor "Homestyle"
end

Now you can write your test like this:

should "return only buttered biscuits" do
  yummy = Factory(:biscuit, :buttered => true)
  gross = Factory(:biscuit, :buttered => false)
  assert_equal [yummy], Biscuit.buttered
end

and never touch it again!

unless you change the behavior of buttered of course…

Bookmark and Share

Other Posts That Might Interest You

  1. Mocking is dead. Long live mocking!
  2. Subdomains, SubdomainFu, and Cucumber
  3. TDD is A Good Thing
  • Taryn: That difference in runtime seems very extreme. I haven't seen jumps like that since starting with factory_girl. In most cases, though, running autotest helps with this, as it will only run tests that are affected by changes first, letting you see failures quickly in most cases.

    Factories do overcome the complications and dependencies found in large systems. In large systems you will have many fixtures for each special case of the data you need, each only identified by the name you give it. So in my examples above, you would have had a buttered_biscuit fixture and an unbuttered_biscuit fixture. On a large system the number of combinations could cause the number of fixtures for that single model to double, triple, etc... multiply this by the number of models and keeping them all straight becomes a major hassle.

    Your factories on the other hand will usually only have one instance per model, identified by the name of the model, each with a set of default attributes. You then override the defaults in your tests as needed. This also has the added benefit of making your tests more readable and easier to understand at a glance.
  • So, the one time I've worked on a project that used factory_girl it was so slow that it really caused an impact on programming style. There were only a few tests, really. At a rough guess I'd say that using fixtures it'd normally take 3-4seconds to run through them all. Using factories it was taking 30-40 seconds.
    That may not seem like a lot, but it's the difference between running your tests every time you change you code... to running them only when you've completed some large chunk of functionality...
    and the project was still growing. By the time it reaches "normal" size I'd be hesitant to run that test suite more often than just before checkin.

    So in my mind, this is a big negative for factories... can you outline what's so good about them that it overcomes this obstacle?

    Now I'm aware, of course, that fixtures can get very complicated for a large system - and the dependencies an become pretty brittle. Is this what factories seeks to overcome? If so - how does it do this instead of, say, just making lots of factories with dependencies instead?

    I'm asking these questions because you clearly have a strong opinion about how good factories are and thus are likely to have the best knowledge of their benefits... and I haven't yet had the pros explained to me in a way that is convincing enough to take the performance hit.

    Cheers,
    Taryn
  • Mmmmmm biscuits...
  • If you favor the plain "create_biscuit" (and don't forget "new_biscuit") creation method style, check out Fixjour: http://nakajima.github.com/fixjour.

    It gives you a nice way to declare your builders, all of the amenities listed above and more!
  • Biscuit'd is still in alpha...
  • Hey guys, thanks for checking out shoulda + factory_girl.

    And...let us know when your biscuits app launches.
blog comments powered by Disqus

Previous post:

Next post: