Real World Example: Using factory_girl to simplify our test setup

by Matthias Marschall on July 31, 2009 · 6 comments

Post image for Real World Example: Using factory_girl to simplify our test setup

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 'test@autoplenum.de'
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:



Related Posts

{ 5 comments… read them below or add one }

1 Henning Koch July 31, 2009 at 9:18 pm

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 Matthias Marschall July 31, 2009 at 9:31 pm

Henning, thanks for letting us know. Looking at the machinist github page, I can see what you mean by leaner syntax :-) And Sham + Faker look really interesting, too.

3 Fernando Alvarez July 6, 2010 at 9:12 am

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

4 Matthias Marschall July 6, 2010 at 12:24 pm

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.

5 Henning Koch July 6, 2010 at 3:18 pm

@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.

Leave a Comment

{ 1 trackback }