【问题标题】:Verifying rspec mocks before the test is over在测试结束之前验证 rspec 模拟
【发布时间】:2019-06-27 14:31:27
【问题描述】:

似乎在测试用例中使用 rspec 模拟的标准方法是执行以下操作:

class MyTest
  def setup
    super
    ::RSpec::Mocks.setup(self)
  end

  def teardown
    super
    begin
      ::RSpec::Mocks.verify
    ensure
      ::RSpec::Mocks.teardown
    end
  end

  test "something"
    foo = MyFoo.new
    expect(foo).to receive(:bar).and_return(42)
    ret = SomeClass.call_bar(foo)

    assert_equal(42, ret)
  end
end

这工作好的。但是,如果SomeClass.call_bar 使用foo.bar 的返回作为返回,并且代码有问题,以至于从未调用过foo.bar,那么我只会因为assert_equal(42, ret) 行而收到失败。我没有看到任何错误,例如:

RSpec::Mocks::MockExpectationError: (foo).bar
    expected: 1 time
    received: 0 times

如果我删除 assert_equal(42, ret) 行,我会得到 rspec 期望错误。但我想验证这两件事,foo.bar 被称为并且最终返回是 42。更重要的是要知道 foo.bar 没有被调用,因为这是 42 的原因没有返回。

如果我期望类似:expect(foo).not_to receive(:bar),那么我确实会在调用源处得到该期望错误,而不是在拆卸过程中稍后。

现在,我可以在调用assert_equal 之前添加::RSpec::Mocks.verify,但这感觉不对。我也不确定此时是否应该清理模拟。

有没有类似的语法:

  test "something"
    foo = MyFoo.new
    ret = nil

    expect(foo).to receive(:bar).and_return(42).during do
      ret = SomeClass.call_bar(foo)
    end

    assert_equal(42, ret)
  end

所以在块传递给during 之后立即进行验证?或者,如果你有多个双打,你可以这样做:

    expect(dbl1).to receive(:one)
    expect(dbl2).to receive(:two)
    expect(dbl3).to receive(:three)

    verify(dbl1, dbl2, dbl3).during do
      my_code
    end

【问题讨论】:

    标签: ruby unit-testing rspec rspec-mocks


    【解决方案1】:

    您正在寻找rspec spies

    间谍是另一种类型的测试替身,支持这一点 通过允许您使用 have_received 来期待在事后收到消息的模式。

    你用allow(...).to receive从你的foo中创建一个部分双重,然后可以断言接收到消息:

    test "something"
      foo = MyFoo.new
      allow(foo).to receive(:bar).and_return(42)
      ret = SomeClass.call_bar(foo)
      expect(foo).to have_received(:bar)
      assert_equal(42, ret)
    end
    

    【讨论】:

    • 这无济于事,因为失败的断言expect(foo).to have_received(:bar) 将中止测试。
    • 我不同意这一点; OP表示知道呼叫没有发生是更重要的断言。我写的测试,如果没有调用该方法,将导致表明对象没有收到消息。如果返回的值取决于收到消息,那么没有收到消息总是意味着第二次测试失败。在这种情况下,聚合故障对调试故障原因没有任何帮助,而且可能会搅浑水。
    • 哦,对不起。我没有注意到断言顺序的变化。您可以稍微修改一下您的答案,以便我可以删除反对票吗?
    • 这非常接近,allow 之前的电话,然后是expect 之后...我会给它一些时间看看是否有其他答案,但这是最接近的可能会被接受。
    【解决方案2】:

    我相信您需要的是汇总失败 https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/aggregating-failures

    在“正常”设置中,任何错误都会中止测试,并且不会检查以后的断言。

    【讨论】:

    • 这并没有回答有关如何实际验证消息调用的问题。
    • 它仍在拆卸阶段进行验证,这是一种有助于在第一个失败的断言时中止测试的解决方案。
    • 这与我需要的一致,但 assert 来自 minitest,expect 来自 rspec,所以我认为它们不会聚合在一起。
    • 你试过了吗?聚合意味着测试在拳头失败后不会中止,因此有可能发生拆解。
    • 我不需要它作为 minitest 的一个功能吗?在assert_equal(42, ret) 失败后,我需要 minitest 继续运行拆解。并且它必须在报告中包含拆解的失败/错误。
    【解决方案3】:

    我不认为有任何内置的方法可以做到这一点,但如果你添加以下类:

    class VerifyDuring
      def initialize(test, objects)
        @test = test
        @objects = objects
      end
    
      def during
        yield
      ensure
        begin
          @objects.each do |object|
            RSpec::Mocks.proxy_for(object).verify
          end
        rescue Exception => e
          @test.flunk e
        end
      end
    end
    
    

    您的测试类使用以下方法:

      def verify(*objects)
        VerifyDuring.new(self, objects)
      end
    

    你可以这样做:

        verify(dbl1, dbl2, dbl3).during do
          my_code
        end
    

    【讨论】:

      猜你喜欢
      • 2011-11-24
      • 1970-01-01
      • 1970-01-01
      • 2020-05-05
      • 1970-01-01
      • 1970-01-01
      • 2012-07-27
      • 2017-05-13
      • 1970-01-01
      相关资源
      最近更新 更多