如何在 RSpec 中多次说“ any_instance”“ should_access”

我有一个导入控制器在轨道,导入多个 csv 文件与多个记录到我的数据库。我想在 RSpec 中测试记录是否是通过使用 RSpec 保存的:

<Model>.any_instance.should_receive(:save).at_least(:once)

然而,我得到的错误说:

The message 'save' was received by <model instance> but has already been received by <another model instance>

一个人为设计的控制器例子:

rows = CSV.parse(uploaded_file.tempfile, col_sep: "|")


ActiveRecord::Base.transaction do
rows.each do |row|
mutation = Mutation.new
row.each_with_index do |value, index|
Mutation.send("#{attribute_order[index]}=", value)
end
mutation.save
end

有没有可能使用 RSpec 来测试它,或者有没有什么变通方法?

44330 次浏览

我终于设法做了一个对我有用的测试:

  mutation = FactoryGirl.build(:mutation)
Mutation.stub(:new).and_return(mutation)
mutation.should_receive(:save).at_least(:once)

存根方法返回多次接收保存方法的单个实例。因为它是一个单一的实例,我可以放弃 any_instance方法,正常使用 at_least方法。

这里有一个更好的答案,可以避免重写: new 方法:

save_count = 0
<Model>.any_instance.stub(:save) do |arg|
# The evaluation context is the rspec group instance,
# arg are the arguments to the function. I can't see a
# way to get the actual <Model> instance :(
save_count+=1
end
.... run the test here ...
save_count.should > 0

似乎存根方法可以附加到约束的任何实例上,do 块可以进行计数,您可以通过检查来断言它被调用的次数是正确的。

更新-新的 rspec 版本需要以下语法:

save_count = 0
allow_any_instance_of(Model).to receive(:save) do |arg|
# The evaluation context is the rspec group instance,
# arg are the arguments to the function. I can't see a
# way to get the actual <Model> instance :(
save_count+=1
end
.... run the test here ...
save_count.should > 0

这样的存根

User.stub(:save) # Could be any class method in any class
User.any_instance.stub(:save) { |*args| User.save(*args) }

那就这样期待吧:

# User.any_instance.should_receive(:save).at_least(:once)
User.should_receive(:save).at_least(:once)

这是使用 any_instance这个要点的简化,因为您不需要代理到原始方法。其他用途请参考该要点。

这里有一个新的语法:

expect_any_instance_of(Model).to receive(:save).at_least(:once)

我的情况有点不同,但是我最终在这个问题上决定把我的答案也放在这里。在我的例子中,我希望存根给定类的任何实例。我在使用 expect_any_instance_of(Model).to时也得到了同样的错误。当我把它改成 allow_any_instance_of(Model).to时,我的问题就解决了。

查看文档了解更多背景知识: https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

这是 Rob 使用 RSpec 3.3的示例,它不再支持 Foo.any_instance。我发现在循环中创建对象时这很有用

# code (simplified version)
array_of_hashes.each { |hash| Model.new(hash).write! }


# spec
it "calls write! for each instance of Model" do
call_count = 0
allow_any_instance_of(Model).to receive(:write!) { call_count += 1 }


response.process # run the test
expect(call_count).to eq(2)
end

你可以试着数一下课堂上 new的数量。这实际上不是测试 save的数量,但可能足够了

    expect(Mutation).to receive(:new).at_least(:once)

如果只有一个期望,那就是它被保存了多少次。那么您可能希望使用 spy()而不是完全运行的工厂,如在 Harm de Wit自己的答案

    allow(Mutation).to receive(:new).and_return(spy)
...
expect(Mutation.new).to have_received(:save).at_least(:once)