Real World Example: Using factory_girl to simplify our test setup

by on July 31, 2009 · 7 comments

When you do integration testing in a ruby on rails application, you don’t want to stub out all involved models. Rails’ built in approach of using fixtures is considered to be sub-optimal and the way to go today is to use factories.

Homegrown Factories

In our application we used to write our own factories, one for each model. But we didn’t build enough intelligence into them to be able to deal with associations automatically. For a 1-to-n relationship, we had to first create n objects of the dependent type and then the owning object, adding it’s children manually. All that happened inside the RSpec before blocks (or separate helper methods, if it became too complex). As there was no standard way of assembling your object networks we had a lot of places where we had similar setups rebuilt from scratch for every other spec. Not exactly DRY.

Factory Girl to the rescue

To improve the situation and cut down the maintenance cost of our specs, I introduced factory_girl. Factory Girl gives you a nice and concise way of defining your object networks and automatically instaniates a whole such network when requesting one of the participating objects. Let’s say you’ve got a similarly complex model as we:

UML Diagram

With factory_girl you have to define one factory per model as we did in our home grown approach. But Factory Girl takes care of associations automatically. Here are the factory definitions for the above graph:

require 'factory_girl'
 
Factory.define :user do |f|
  f.sequence(:user_name){ |n| "fooAPL#{n}" }
  f.password 'ups'
  f.email '[email protected]'
end
 
Factory.define :trademark do |f|
  f.sequence(:name) {|n| "TRADEMARK#{n}"}
end
 
Factory.define :model do |f|
  f.name "The Model"
  f.association :trademark
  f.association :user
end
 
Factory.define :car do |f|
  f.association :trademark
  f.association :model
  f.name "Carrr"
end

If you do not care about the exact attributes of the objects in the graph (you just expect them to be there with their default values), it’s now as easy as

Factory(:car)

to construct the whole object network. Even things like:

car.model.trademark

work as expected.

If you need more control over specific attributes of participating objects, just let factory_girl create them and use them in later factory_girl calls:

tm = Factory(:trademark, :name => "A very special name")
model = Factory(:model, :name => "Another special name")
car = Factory(:car, :model => model, :trademark => tm)

In that way you can customize certain attributes of your objects on the fly.

Room for improvement

Pushing Factory Girl more and more to the edge cases, we discovered that its support for polymorphic associations is not yet perfect. And its ability to return stubs instead of real ActiveRecord objects seems a little over simplified in comparison to using a fully fledged stubbing framework (which you can use easily in conjunction with factory girl). It would be great if factory girl would use one of the common mock frameworks for its factories.

Factory girl is a great tool, which enabled us to drop hundreds of lines of duplicated and overcomplicated setup code. It’s really so much nicer and cleaner.

What are your experiences with fixtures and factories? Let us know in the comments!

Did you enjoy this article? Get new articles for free by email:

Comments

  1. says

    We’ve been using Factory Girl with great success for something like a year now. Now we started using Machinist for new projects, which takes a lot of ideas from Factory Girl and improves on its syntax. Machinist also comes with the awesome “Sham” class, which (in conjunction with Faker) gives you random but unique test data to populate your models with required attributes you don’t care about.

  2. Fernando Alvarez says

    Hi, thanks for the article.
    Do you know if there’s a way to create a new model of the same trademark as the previous user without setting all the stuff manually? I mean, is there a way to pass a block or something like that while building the associations in the factory?
    I know I could use something like
    other_car_model = Factory(:model, :car => car.model, :trademark => car.model.trademark)
    other_user = Factory(:model => other_car_model)

    But if the business model is a little more complex (with more models and more associations) this becomes a little bit painful.

    Thanks in advance

  3. says

    Hm, usually we use defaults specified in the factory itself. If you really need to setup a special trademark which you need to reuse over multiple cars the only thing you could do is:

    tm = Factory(:trademark)
    car = Factory(:car, :trademark => tm, :name => "car")
    other_car = Factory(:car, :trademark => tm, :name => "other")

    But I fear that is not really better than your solution.
    So, setting up sane defaults and sticking to them where ever possible is the best advice I can come up with right now, sorry.

  4. says

    @Fernando: You can always write a method that does what you cannot describe in a factory:

    def make_car_with_last_trademark(options)
    options[:trademark] ||= Trademark.last || Factory(:trademark)
    Factory(:car, options)
    end

    With machinist you could embed this behavior directly into a factory (blueprint in machinist-lingo) because every attribute can take a block. I’m not sure about your options with factory_girl.

  5. John says

    I’d like to be able to create two objects, A and B, and relate them both to Object C in my factories so I can dry up my code.

    I’m running into scenarios where Object A is related to one instance of Object C, and Object B is related to another instance of Object C, when I’d like them to both be relating to the same instance of Object C.

Trackbacks

  1. [...] Ultimately this gives the developer more power and makes setting up data for a test much simpler. With a factory, you simply declare the data necessary for a given test. I discovered factory_girl a few years ago and it’s been a reliable part of my testing [...]

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>