!transactional fixtures

Thu May 01 20:13:00 UTC 2008

I am referring to transactional fixtures, and I had a mistaken understanding of how they worked. For my own notes, here’s what I learned.

By default, TestCase and RSpec use transactional fixtures. This is set in the respective helper file (test/test_helper.rb or spec/spec_helper.rb). Transactional fixtures is a terrible name for this feature, because the fixtures are not transactional. What transactional fixtures actually means is that each test method is executed in a database transaction which is rolled back when the test is finished.

Here’s what happens when you have a test class with several test methods and one fixture (let’s say :users) specified

  • all rows are deleted from the users table
  • an insert is executed for each user listed in the :users fixture
  • for each test method in the test case
    • a transaction is started
    • the test is run
    • the transaction is rolled back

This means that when the class is finished executing, all the fixture data that was loaded is left in the database.

There’s a gotcha lurking here, which is what bit me. Let’s say you have two models as follows

class User < ActiveRecord::Base
  has_many :articles
end

class Zebra < ActiveRecord::Base
  belongs_to :user
end

class UserTest < Test::Unit::TestCase
  fixtures :users
  # test methods here
end

class ZebraTest < Test::Unit::TestCase
  fixtures :zebras
  # test methods here
end

When you run rake test:models, all your zebra tests pass. However, if you rake db:test:prepare and run ruby test/unit/zebra_test.rb, suddenly tests are failing. This is because when you run the entire suite, the User test case is running first and its fixture, :users, is getting loaded into the database. When the suite gets to the Zebra test case, the test methods that rely on rows in the users table find the data that was left over from the User test case and so they work. However, when you run the Zebra test case in isolation on a fresh test database, that user data is not loaded, so the tests fail.

This has led to a new best practice for me when writing tests. I always make sure I’m running the TestCase against a clean database while I’m writing it. I do this by frequently running rake db:test:prepare. That way I know that the TestCase has the fixture data it needs to run on its own and is not dependent on data that might be left over from the fixtuers of other tests.

Tags: rspec testing fixtures

Comments