From Fixtures to Factories «

Code: , , , , , ,
Comments Off on From Fixtures to Factories

Automated tests need example data, and it’s a pain to have to construct a complete object in every test, especially when there are a lot of non-optional fields.

The standard improvement is to use fixtures, a file with some example data,
that your tests can load by giving the name of a fixture. Here’s one of the
fixtures for a gamer:

alice: # alice is heavily involved
id: 1
handle: alice
password: red queen
teaser: Follow the white rabbit.
profile: Not so plain, after all.
location_id: 3
last_login: 2006-10-30 11:56:08
created_at: 2006-10-30 11:56:08
location_at: 2006-10-30 11:56:08
tag_text: dnd, wod
mail_on_message: true
mail_on_nearby_discussion: true

This is straightforward, but location_id is the id of another fixture in another file. As you can imagine it’s pretty easy to get them out-of-sync and break some tests. I end up leaving little comments in the YAML to remind myself of the cross-reference.

It’s awfully tempting to reuse fixtures from test to test. Maybe Alice was written to test logging in with a username and password and next I’m writing the test of logging in with an email address and a password. I could copy and paste Alice to another fixture to reuse in that test, but as a programmer I have a pathological aversion to copy and paste. Alice is going to get reused (most of those fields are optional and were used by very different tests), and after this happens a few times it’s hard to change a fixture without breaking an unrelated test.

I’ve gotten a lot of use out of fixtures as I’ve developed the habit of testing all my code, but reuse has made my tests somewhat brittle. After a bit of research, I’ve moved over to using factories.

Here’s the factory for a gamer:

Factory.sequence :handle { |n| "Gamer_#{n}" }

Factory.define :gamer do |g|
g.handle { :handle }
g.mail { |m| "#{m.handle}" }
g.password 'secret'

Only the essential, required fields are listed. Using this simple template, the tests themselves specify the values of the fields they’ll be testing.

def test_login_with_valid_username_and_password
gamer = Gamer.create :handle => 'Alice', :password => 'red queen'
def test_login_with_valid_email_and_password
gamer = Gamer.create :mail => '', :password => 'red queen'

Ideally, changing a fixture wouldn’t break any tests at all. And factory_girl (what I’m using for factories in Rails) makes associations easy:

Factory.define :post do |p|
p.association :discussion, :factory => :discussion
p.association :poster, :factory => :gamer
p.body "Post Body"

Now my tests include the specific data they care about, making them easier to understand and improve. And they don’t interrelate, so they’re much more reliable. I do still use fixtures, but now they’re exclusively for tests that need to deal with real-world examples.