【问题标题】:RSpec allow/expect vs just expect/and_returnRSpec 允许/期望与仅期望/and_return
【发布时间】:2015-01-18 03:41:16
【问题描述】:

在 RSpec,特别是版本 >= 3 中,有什么区别:

  • 使用allow 设置带有返回测试替身的参数的消息预期,然后使用expect 对返回的测试替身进行断言
  • 只需使用expect 设置带参数的期望并返回测试替身

还是只是语义?我知道使用expect 提供/指定返回值是the syntax in RSpec mocks 2.13,但据我所知,the syntax changed in RSpec mocks 3 使用allow

但是,在下面的(通过)示例代码中,使用allow/expect 或仅使用expect/and_return 似乎会产生相同的结果。如果一种语法比另一种更受青睐,也许我会期望会有某种弃用通知,但既然没有,这两种语法似乎都被认为是有效的:

class Foo
  def self.bar(baz)
    # not important what happens to baz parameter
    # only important that it is passed in
    new
  end

  def qux
    # perform some action
  end
end

class SomethingThatCallsFoo
  def some_long_process(baz)
    # do some processing
    Foo.bar(baz).qux
    # do other processing
  end
end

describe SomethingThatCallsFoo do
  let(:foo_caller) { SomethingThatCallsFoo.new }

  describe '#some_long_process' do
    let(:foobar_result) { double('foobar_result') }
    let(:baz) { double('baz') }

    context 'using allow/expect' do
      before do
        allow(Foo).to receive(:bar).with(baz).and_return(foobar_result)
      end

      it 'calls qux method on result of Foo.bar(baz)' do
        expect(foobar_result).to receive(:qux)
        foo_caller.some_long_process(baz)
      end
    end

    context 'using expect/and_return' do
      it 'calls qux method on result of Foo.bar(baz)' do
        expect(Foo).to receive(:bar).with(baz).and_return(foobar_result)
        expect(foobar_result).to receive(:qux)
        foo_caller.some_long_process(baz)
      end
    end
  end
end

如果我故意通过将传入的 baz 参数更改为不同的测试替身来使测试失败,则错误几乎相同:

  1) SomethingThatCallsFoo#some_long_process using allow/expect calls quux method on result of Foo.bar(baz)
     Failure/Error: Foo.bar(baz).qux
       <Foo (class)> received :bar with unexpected arguments
         expected: (#<RSpec::Mocks::Double:0x3fe97a0127fc @name="baz">)
              got: (#<RSpec::Mocks::Double:0x3fe97998540c @name=nil>)
        Please stub a default value first if message might be received with other args as well.
     # ./foo_test.rb:16:in `some_long_process'
     # ./foo_test.rb:35:in `block (4 levels) in <top (required)>'

  2) SomethingThatCallsFoo#some_long_process using expect/and_return calls quux method on result of Foo.bar(baz)
     Failure/Error: Foo.bar(baz).qux
       <Foo (class)> received :bar with unexpected arguments
         expected: (#<RSpec::Mocks::Double:0x3fe979935fd8 @name="baz">)
              got: (#<RSpec::Mocks::Double:0x3fe979cc5c0c @name=nil>)
     # ./foo_test.rb:16:in `some_long_process'
     # ./foo_test.rb:43:in `block (4 levels) in <top (required)>'

那么,这两个测试之间是否有任何真正的区别,无论是结果还是表达意图,还是仅仅是语义和/或个人偏好? allow/expect 应该在一般情况下使用而不是 expect/and_return,因为它似乎是替换语法,还是它们每个都意味着在特定的测试场景中使用?

更新

看了Mori's answer的后,我把上面示例代码中的Foo.bar(baz).qux这一行注释掉了,得到如下错误:

  1) SomethingThatCallsFoo#some_long_process using allow/expect calls qux method on result of Foo.bar(baz)
     Failure/Error: expect(foobar_result).to receive(:qux)
       (Double "foobar_result").qux(any args)
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # ./foo_test.rb:34:in `block (4 levels) in <top (required)>'

  2) SomethingThatCallsFoo#some_long_process using expect/and_return calls qux method on result of Foo.bar(baz)
     Failure/Error: expect(Foo).to receive(:bar).with(baz).and_return(foobar_result)
       (<Foo (class)>).bar(#<RSpec::Mocks::Double:0x3fc211944fa4 @name="baz">)
           expected: 1 time with arguments: (#<RSpec::Mocks::Double:0x3fc211944fa4 @name="baz">)
           received: 0 times
     # ./foo_test.rb:41:in `block (4 levels) in <top (required)>'
  • allow 规范失败,因为 foobar_result 双精度永远不能代替 Foo.bar(baz) 的结果,因此永远不会调用 #qux
  • expect 规范在 Foo 从未收到 .bar(baz) 时失败,所以我们甚至没有达到询问 foobar_result 双精度的地步

有道理:这不仅仅是语法更改,而且expect/and_return 的目的确实与allow/expect 不同。我真的应该检查最明显的地方:RSpec Mocks README,特别是以下部分:

【问题讨论】:

    标签: ruby testing rspec mocking rspec3


    【解决方案1】:

    参见经典文章Mocks Aren't Stubsallow 做一个存根,而expect 做一个模拟。那就是allow 允许一个对象返回 X 而不是它会返回未存根的任何东西,而expect 是一个allow 加上 对某些状态或事件的期望。当你写

    allow(Foo).to receive(:bar).with(baz).and_return(foobar_result)
    

    ...您告诉规范环境修改Foo 以在收到:barbaz 时返回foobar_result。但是当你写

    expect(Foo).to receive(:bar).with(baz).and_return(foobar_result) 
    

    ...您也在做同样的事情,并告诉规范失败除非Foo 收到:barbaz

    要查看差异,请尝试在 Foo 没有接收 :barbaz 的示例中。

    【讨论】:

    • 'expect' 优于 'allow' 的一个附带优势 - 除了实现细节 - 如果 'allow' 变得与您的测试无关,它就会变成计算机不会警告您的死代码关于。动态语言有一个优势,那就是用一个通用的委托对象包装它们是微不足道的,如果委托者从未用于转发消息,该对象将在销毁时爆炸。
    猜你喜欢
    • 2014-06-23
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    相关资源
    最近更新 更多