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


73

我在rails中有一个导入控制器,该控制器将具有多个记录的多个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对此进行测试,或者有任何解决方法?


您正在使用哪个版本的RSpec,看到的失败消息是什么?
David Chelimsky

rspec(2.8.0),消息为:消息“保存”已由<模型实例>接收,但已由<另一个模型实例>接收
Harm de Wit 2012年

这是预期的行为。any_instance的要点是不必知道哪个单个实例在期待什么,但是它仍将其限制为一个实例。
David Chelimsky 2012年

8
这是预期的行为-理所当然-,但是如果您要测试这一点,它并不是很有用。而且似乎没有其他方法可以放松一个实例的约束,例如“ many_instances”。
罗布

Answers:


46

有一个新的语法:

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

3
rspec的设计人员不鼓励使用expect_any_instance_of。这是doc链接
泰勒·科利尔

@TylerCollier是的,我完全同意。从该文档中可以得出:“使用此功能通常是一种设计上的气味。可能是您的测试尝试做太多事情,或者被测试的对象太复杂了。”
muirbot'2

20
这实际上对我不起作用;与原始问题相同的错误。使用RSpec 3.3.3。
牛排追逐者

然后,这听起来像是合法失败。有两个实例正在接收该消息。
muirbot

50

这是一个更好的答案,它避免了必须覆盖: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

似乎stub方法可以附加到没有约束的任何实例,并且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

很好在any_instance上使用存根!看起来比我的解决方案更好,更动态。
Harm de Wit 2012年

1
即使您的技巧不那么精明,我也不喜欢计数器,通过代理类方法[这里] [ stackoverflow.com/a/15038406/619510],我保留了相同的语法。
michelpm

辉煌!为此+1。:D
没什么特别的,在这里

15

我终于设法进行了一个适合我的测试:

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

存根方法返回一个实例,该实例多次接收save方法。因为它是单个实例,所以我可以删除该any_instance方法并at_least正常使用该方法。


12

这是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

11

像这样的存根

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,因为你不需要代理的原始方法。请参阅该要旨以用于其他用途。


1
优秀的。将方法代理到已知实例非常有用。您可以轻松地在测试double上存根(:save)而不是User类。
马特·康诺利

奇迹般有效!谢谢!:-)
wrzasa 2015年

只需使用allowallow_any_instance_of替代stubany_instance.stub因为前者现在已过时(见:github.com/rspec/...
wrzasa

3

我的情况有些不同,但我最终想到了这个问题,想把答案也放到这里。就我而言,我想对给定类的任何实例进行存根。我使用时遇到了同样的错误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


谢谢!这对我来说是最有用的答案。
拉斐尔·西埃拉克

来自文档:“ rspec-mocks API是为单个对象实例设计的,但是此功能可在整个对象类上运行。因此,存在一些语义上令人困惑的边缘情况。例如,expect_any_instance_of(Widget).to receive(:name).twice尚不清楚每个特定实例是否有望获得name两次,或者如果两个接收总预期(这是前者。)”
拉法尔Cieślak

0

您可以尝试计算new班级上的人数。那实际上不是测试saves的数量,但可能足够

    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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.