【问题标题】:How to say "any_instance" "should_receive" any number of times in RSpec如何在 RSpec 中多次说“any_instance”“should_receive”
【发布时间】:2012-04-05 18:11:07
【问题描述】:

我在 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,您看到的失败消息是什么?
  • rspec (2.8.0) 并且消息是:消息“保存”已由 接收,但已被 接收
  • 这是预期的行为。 any_instance 的要点是不必知道哪个单个实例正在期待某事,但它仍然将其限制为一个实例。
  • 这是预期的行为 - 授予 - 但如果你想测试它不是很有用。而且似乎没有任何其他方法,例如“many_instances”可以放松一个实例的约束。

标签: ruby-on-rails testing rspec mocking mocha.js


【解决方案1】:

这是一个更好的答案,可以避免重写 :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

【讨论】:

  • 在 any_instance 上使用存根真好!看起来比我的解决方案更好,更有活力。
  • 尽管你的不那么老套,但我不喜欢计数器,通过代理类方法[这里][stackoverflow.com/a/15038406/619510]我保持相同的语法。
  • 太棒了!为此采取+1。 :D
【解决方案2】:

有一个新的语法:

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

【讨论】:

  • rspec 的设计者不鼓励使用expect_any_instance_of。这是link to the doc
  • @TylerCollier 是的,我完全同意。来自该文档:“使用此功能通常是一种设计气味。可能是您的测试试图做太多事情,或者被测对象太复杂。”
  • 这实际上对我不起作用;与原始问题相同的错误。使用 RSpec 3.3.3。
  • 那么这听起来像是一个合法的失败。有两个实例正在接收消息。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      这样的存根

      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)
      

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

      【讨论】:

      • 优秀。将方法代理到已知实例效果很好。您可以轻松地在测试替身而不是 User 类上存根(:save)。
      • 像魅力一样工作!谢谢! :-)
      • 只需使用allowallow_any_instance_of 而不是stubany_instance.stub,因为前者现在已被弃用(请参阅:github.com/rspec/…
      【解决方案6】:

      我的情况有点不同,但我最终在这个问题上也想在这里放弃我的答案。就我而言,我想存根给定类的任何实例。我在使用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 是为单个对象实例设计的,但此功能适用于整个对象类。因此存在一些语义上令人困惑的边缘情况。例如在 @ 987654324@ 尚不清楚每个特定实例是否预计会收到两次name,或者是否预计总共收到两次。(是前者。)“
      【解决方案7】:

      您可以尝试计算班级中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)
      

      【讨论】:

        猜你喜欢
        • 2010-11-22
        • 1970-01-01
        • 1970-01-01
        • 2012-12-31
        • 2014-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-11
        相关资源
        最近更新 更多