Why isn't factory_girl operating transactionally for me? - rows remain in database after tests

Peter Nixey picture Peter Nixey · Aug 16, 2010 · Viewed 11.3k times · Source

I'm trying to use factory_girl to create a "user" factory (with RSpec) however it doesn't seem to be operating transactionally and is apparently failing because of remnant data from previous tests in the test database.

Factory.define :user do |user|
  user.name                   "Joe Blow"
  user.email                  "[email protected]" 
  user.password               'password'
  user.password_confirmation  'password'
end

@user = Factory.create(:user)

Running the first set of tests is fine:

spec spec/ 


...
Finished in 2.758806 seconds

60 examples, 0 failures, 11 pending

All good and as expected, however running the tests again:

spec spec/ 
...
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/validations.rb:1102:in `save_without_dirty!': Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/dirty.rb:87:in `save_without_transactions!'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:182:in `transaction'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:208:in `rollback_active_record_state!'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
    from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/proxy/create.rb:6:in `result'
    from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/factory.rb:316:in `run'
    from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/factory.rb:260:in `create'
    from /Users/petenixey/Rails_apps/resample/spec/controllers/users_controller_spec.rb:7
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:183:in `module_eval'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:183:in `subclass'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:55:in `describe'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_factory.rb:31:in `create_example_group'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/dsl/main.rb:28:in `describe'
    from /Users/petenixey/Rails_apps/resample/spec/controllers/users_controller_spec.rb:3
    from /Library/Ruby/Gems/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:147:in `load_without_new_constant_marking'
    from /Library/Ruby/Gems/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:147:in `load'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load_files'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `each'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `load_files'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/options.rb:133:in `run_examples'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/command_line.rb:9:in `run'
    from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/bin/spec:5
    from /usr/bin/spec:19:in `load'
    from /usr/bin/spec:19

Fix attempt - use Factory.sequence

Since I have a uniqueness constraint on my email field I attempted to fix the problem by using the sequence method of factory_girl:

Factory.define :user do |user|
  user.name                   "Joe Blow"
  user.sequence(:email) {|n| "joe#{n}@blow.com" }
  user.password               'password'
  user.password_confirmation  'password'
end

I then ran

rake db:test:prepare
spec spec/
.. # running the tests once executes fine
spec spec/
.. # running them the second time produces the same set of errors as before

Users seem to remain in the database

If I look at the /db/test.sqlite3 database it seems that the row for the test user is not being rolled back from the database between tests. I thought that these tests were supposed to be transactional but they don't seem to be so for me.

This would explain why the test runs correctly the first time (and if I clear the database) but fails the second time.

Can anyone explain what I should change to ensure that the tests run transactionally?

Answer

Peter Nixey picture Peter Nixey · Aug 17, 2010

Finally fixed this and I hope I can save someone the six hours of debugging it took me to figure it out.

By a) getting lucky and ending up with a version of code that worked and b) stripping both sets of code down this is what I found:

Test that chokes up

require 'spec_helper'

describe UsersController do

  @user = Factory.create(:user) 
end

Test that works

require 'spec_helper'

describe UsersController do

  it "should make a factory models without choking" do
    @user = Factory.create(:user)   
  end
end

The transaction is defined by the it "should do something" do... statement. If you instantiate the factory outside that statement it turns out not to be transactional.

You can also put it outside the "it should.." block as long as it's in a "before..end" block

require 'spec_helper'

describe UsersController do

  before(:each) do
    @user = Factory.create(:user) 
  end

  it 'should make a factory without choking' do
    puts @user.name
    # prints out the correnct name for the user
  end
end

On experimenting, it seems to be valid to define a user outside of an "it should do..end" block as long as it's in a "before.. end" block. I guess this is only executed in the scope of the "it should do..end" block and therefore works fine.

[Thanks to @jdl for his (also correct) suggestion]