Kill your fixtures and spec happily
ben on April 5th, 2008 1 Comment
The trouble w/ fixtures
I don’t use fixtures in my specs. At all. The fundamental issue with fixtures is that they setup a state for your database as a whole (or at least the tables you loaded). So when you use fixtures all your specs are running against essentially the same state. As you build your application and things get more complex your fixtures become more complex and your state grows and grows. The needs of your specs, however, should pretty much stay the same. So when you go back and track down an issue causing some spec to blow up you have to deal with that spec running in a state that’s way beyond it’s needs.
Brittle specs
For example, lets stay I’m writing a spec to ensure that book.authors.living does not return any deceased authors. The state that I need to spec this is very simple: 1 Book with 1 living Author and 1 dead Author. Then I just need to make sure that when I run book.authors.living I don’t get the dead Author back. It might look something like this:
describe Book do describe "with authors" do fixtures :books, :authors it "should only find living authors" do Book.find_by_title("Foo's Diary").authors.living.should == [Author.find_by_name('Jim')] end end end
Assuming I know that Jim is alive this would be a good spec. The problem is as I write specs I’m going to add more and more authors and this spec will quickly blow up. It’s not blowing up because the .living method broke though, it’s just blowing up because it’s no longer running in the state it expected.
Kill your fixtures
Without fixtures I would do something like:
describe Book do describe "with authors" do before :each do @book = Book.create! :title => "Foo's Diary" @book.authors << (@jim = Author.new(:name => 'Jim', :dead => false)) @book.authors << (@jeff = Author.new(:name => 'Jeff', :dead => true)) end it "should only find living authors" do @book.reload.authors.living.should == [@jim] end end end
Now my spec will always run in the state I gave it. So the things I setup for my other specs won’t come back and blow this spec up. Furthermore, the state isn’t buried in multiple fixture files. It’s defined plainly right above the spec(s) which use it.
Simplify your life even more
Once you ditch your fixtures you’ll find a new love for writing specs. There’s a couple handy tools to simplify your life further though:
Factories
Dan Manges blogged about using factories to simplify creation of classes and to provide defaults. Since then there’s been a few plugins developed for quick and easy factories. We’ve been using Scott Taylor’s Fixture Replacement.
add!
add is similar to the << method of has_many associations except it sacrifices chainability in order to return the concatenated object. add! goes a step further and throws an exception if the concatenation failed. This is useful in specs because the exception (probably due to a validation error) will occur in your state setup and you will see the error message in your output when you run your specs. So the reason your spec just blew up is displayed for you.
With Fixture Replacement and add! the example above would look something like:
describe Book do describe "with authors" do before :each do @book = create_book :title => "Foo's Diary" @jim = @book.authors.add! new_author(:name => 'Jim') @jeff = @book.authors.add! new_author(:name => 'Jeff', :dead => true) end it "should only find living authors" do @book.reload.authors.living.should == [@jim] end end end

Useful tips - we’ve been doing this for a while, but should probably investigate Fixture Replacement to make our lives simpler.