34

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                  "joe@blow.com" 
  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?

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76
Peter Nixey
  • 16,187
  • 14
  • 79
  • 133
  • Can I suggest that remove mentions of factory-girl in the question & tag? Since the core issue really is all about creating records in the wrong block. Same can happen if a record is created by the build-in create() call. – lulalala Sep 03 '14 at 04:27

5 Answers5

52

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]

Peter Nixey
  • 16,187
  • 14
  • 79
  • 133
  • 6
    any idea on how this behaves inside a let ? like so: let(:something) {Factory(:something)} – Cezar Halmagean Sep 10 '10 at 14:08
  • 2
    Very helpful, thanks Peter. Cezar, re. `let(:foo) {Factory(:foo)}`, it seems that `foo` will be created (the factory invoked) when first referenced, so it's the reference to foo that must appear inside an example or a `before` block. The DB is still rolled back at the end of the example. – Mark Berry Apr 16 '12 at 23:38
  • 2
    You can also use `let!` if you want foo to be created immediately. (And yes, that does work with transactions.) – Ajedi32 Aug 31 '12 at 21:24
  • Thanks MarkBerry and Ajedi32, I was wondering why let wasn't persisting my machinist Foo.make! to the db, but putting it in a before block did. – poetmountain Mar 26 '13 at 03:56
  • Never create any variables you want to test against outside an it, before, or let block. Assigning variables you want to test against (as in your first example) is a total no-no. The reason for this is that transactional test runs are very picky, and run setup & tear down every time EACH TEST IS RUN. This is a good thing because it means you have a clean database. What you're doing in 1st example is a no-no (obviously doesn't work) because it creates those objects when the FILE LOADS (literally when ruby reads the 'describe' block) which is only once at the start of all tests. – Jason FB Jul 25 '14 at 15:18
  • If you really want objects created globally before every test, see "Global after and before blocks" section here http://old.rspec.info/documentation/before_and_after.html – Jason FB Jul 25 '14 at 15:20
  • Watch your `all`s and `each`es, people! I was experiencing the same bug, except I had my `create` call in a global before block—thinking I was safe—as @jason-fb suggests. Unfortunately, changing the block to a `before(:each)` was the solution. – mklbtz Jul 26 '16 at 20:54
  • yea that was two years ago I barely remember that haha. @mklbtz -- just use database_cleaner and prefer the before(:each). I only use before(:all) when there are no alternatives and typically for non-data related stuff (like writing files to disk) – Jason FB Aug 03 '16 at 00:18
  • I have also noticed that the `before` block has to be a `before(:each)` block. Seems that models instantiated in a `before(:all)` get kept. – Kyle Anderson May 07 '20 at 23:12
21

See my blog entry on the difference between using before :all and before :each with regard to transactions: http://mwilden.blogspot.com/2010/11/beware-of-rspecs-before-all.html. In a nutshell, before :all is not transactional, and data created there will stick around after the test is run.

Mark Wilden
  • 2,080
  • 1
  • 15
  • 15
  • 1
    An important distinction, thanks for that clarification. Checking test.log, it appears that the `before :all` still starts a transaction (`BEGIN`), but then it runs a `COMMIT` rather than a `ROLLBACK`, which is why the data persists. – Mark Berry Apr 16 '12 at 23:42
4

In spec/spec_helper.rb, make sure you have the following

RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

This seems to solve the problem for me.

Chris W
  • 938
  • 7
  • 13
3

Inside of test/test_helper.rb make sure that you have the following.

class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  #...
end

Despite the name "fixtures" this works with factory_girl as well.

jdl
  • 17,702
  • 4
  • 51
  • 54
  • 1
    Even though it looks right, that doesn't seem to fix the problem. I'm working through Michael Hartl's RailsTutorial and doing a second writing of the code he originally wrote. When I copied it word-for-word it all worked fine and I know that I never added the user_transactional_fixtures flag. Now though for some reason it's failing on the rewrite project even if I add that. – Peter Nixey Aug 16 '10 at 23:14
  • Commenting here because I too saw the same puzzling behavior. Otherwise, the tuorial was awesome. – Perry Horwich Apr 29 '11 at 17:11
0

I ran into these same symptoms when upgrading a project from Rails 3 to Rails 4. I had done a bundle install, and development mode seemed to be working fine, but I wouldn't get transactional behavior in tests. It turns out doing a bundle update solved the problem.

mgrant
  • 991
  • 7
  • 10