为什么我的factory_girl没有在事务中运行?测试后数据库中仍有行。

34

我正在尝试使用factory_girl创建一个“用户”工厂(使用RSpec),但似乎它并没有以事务方式运行,显然由于测试数据库中之前测试的残留数据而失败了。

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)

运行第一组测试是可以的:

spec spec/ 


...
Finished in 2.758806 seconds

60 examples, 0 failures, 11 pending

一切都很好,和预期的一样,不过需要再次运行测试:

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

修复尝试 - 使用Factory.sequence

由于我的email字段有一个唯一性约束,我尝试使用factory_girl的sequence方法来解决这个问题:

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

我随后运行了

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

用户似乎仍然保留在数据库中

如果我查看/db/test.sqlite3数据库,似乎测试用户的行没有在测试之间从数据库中回滚。我认为这些测试应该是事务性的,但是对我来说似乎不是这样。

这就解释了为什么第一次测试运行正确(并且如果我清除数据库),但第二次失败。

有人能解释一下我应该更改什么以确保测试以事务方式运行吗?


我可以建议在问题和标签中删除factory-girl的提及吗?因为核心问题实际上与在错误的代码块中创建记录有关。如果使用内置的create()调用创建记录也可能发生同样的情况。 - lulalala
5个回答

52

终于解决了这个问题,我希望我能为其他人省下六个小时的调试时间。

通过a)运气好得到代码的一个版本可用,并且b)将两组代码都精简化,我发现了以下内容:

测试失败

require 'spec_helper'

describe UsersController do

  @user = Factory.create(:user) 
end

测试有效

require 'spec_helper'

describe UsersController do

  it "should make a factory models without choking" do
    @user = Factory.create(:user)   
  end
end
交易由"it 'should do something' do..."语句定义。如果你在该语句之外实例化工厂,则不会是事务性的。 只要它位于"before...end"块中,也可以将其放在"it should..."块之外。
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

经过实验,似乎可以在“it should do..end”块之外定义一个用户,只要它在“before..end”块中。我猜这只在“it should do..end”块的作用域中执行,因此可以正常工作。

[感谢 @jdl 提供的(同样正确的)建议]


6
对于这个问题,您想知道它在 let 块内的表现如何?类似于以下写法:let(:something) {Factory(:something)}。 - Cezar Halmagean
2
非常有帮助,谢谢Peter。关于let(:foo) {Factory(:foo)},Cezar说,当第一次引用时将创建foo(调用工厂),因此必须在示例或before块内出现对foo的引用。数据库仍会在示例结束时回滚。 - Mark Berry
2
如果你想让foo立即被创建,你也可以使用let!。(是的,在事务中也可以这样做。) - Ajedi32
感谢MarkBerry和Ajedi32,我想知道为什么let不能将我的机工Foo.make!持久化到数据库中,但将其放在before块中可以。 - poetmountain
是的,那是两年前的事了,我几乎不记得了哈哈。@mklbtz -- 只需使用database_cleaner并优先选择before(:each)。当没有其他选择时,我只会使用before(:all),通常用于非数据相关的内容(例如将文件写入磁盘)。 - Jason FB
显示剩余4条评论

21

1
一个重要的区别,感谢澄清。查看test.log,似乎before :all仍然会启动一个事务(BEGIN),但它运行了一个COMMIT而不是一个ROLLBACK,这就是数据持久化的原因。 - Mark Berry

4
spec/spec_helper.rb 文件中,请确保您拥有以下内容。
RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

这对我似乎解决了问题。


3
test/test_helper.rb 文件中,请确保有以下内容。
class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  #...
end

尽管名字叫做“fixtures”,但它也适用于factory_girl

1
尽管看起来没问题,但似乎并没有解决问题。我正在学习Michael Hartl的Rails教程,并对他最初编写的代码进行第二次编写。当我逐字复制时,一切都正常工作,我知道我从未添加过user_transactional_fixtures标志。但现在,即使我添加了它,重写项目仍然失败了。 - Peter Nixey
在这里发表评论,因为我也看到了相同令人困惑的行为。否则,这个教程非常棒。 - Perry Horwich

0
我在将一个项目从 Rails 3 升级到 Rails 4 时遇到了同样的症状。我运行了 bundle install,开发模式似乎工作正常,但是测试中没有事务行为。结果发现进行 bundle update 解决了问题。

你有没有想过在 bundle update 过程中更新了哪些 gem 包? - Isaac Betesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接