RSpec允许/期望vs期望/ and_return

Pau*_*nti 34 ruby testing rspec mocking rspec3

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

  • 使用allow设置了返回测试双打参数,消息的预期,然后使用expect,使在返回的测试双打断言
  • 只是expect用于设置参数的期望并返回测试双精度

或者只是语义学?我知道提供/指定返回值expectRSpec模拟2.13中的语法,但据我所知,RSpec模拟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
Run Code Online (Sandbox Code Playgroud)

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

  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)>'
Run Code Online (Sandbox Code Playgroud)

那么,这两个测试之间是否有任何真正的差异,无论是结果还是表达意图,还是只是语义和/或个人偏好?应该allow/ 通常expect使用expect/ and_return因为它似乎是替换语法,或者它们中的每一个是否意味着在特定的测试场景中使用?

更新

在阅读了Mori的答案之后,我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)>'
Run Code Online (Sandbox Code Playgroud)
  • allow规范将失败,因为foobar_result双永远不会站在对的结果Foo.bar(baz),因此从来没有#qux要求它
  • expect规范在点失败Foo永远不会接收.bar(baz),所以我们甚至没有去询问点foobar_result

有道理:它不仅仅是语法变化,而且expect/ and_return确实有一个与allow/ 不同的目的expect.我真的应该检查一下最明显的地方:RSpec Mocks README,特别是以下几节:

Mor*_*ori 93

请参阅经典文章Mocks Are Not Stubs.allowexpect制作模拟时制作存根.即allow允许一个对象返回,而不是不管它会返回unstubbed X,并且expectallow 加上一些状态或事件的预期.当你写作

allow(Foo).to receive(:bar).with(baz).and_return(foobar_result)
Run Code Online (Sandbox Code Playgroud)

......你告诉规范环境修改Foo返回foobar_result当它接收:barbaz.但是当你写作

expect(Foo).to receive(:bar).with(baz).and_return(foobar_result) 
Run Code Online (Sandbox Code Playgroud)

...你做同样的,再加上告诉规范失效,除非 Foo接收:barbaz.

要看到其中的差别,同时尝试在例子,其中Foo没有收到:barbaz.

  • 这是一个错误的假设 - “expect”不是模拟,也不是存根,它是一个期望,可以设置为等待调用存根或真实方法。RSpec 文档不是最清晰的文档,因此需要澄清一下:存根是“allow(obj).to receive(:method)”。模拟是“allow(obj).to receive(:method).and_return(something)”。 (3认同)
  • 除了实现细节之外,“期望”相对于“允许”的一个附带优点是,如果“允许”与您的测试无关,它就会成为计算机不会警告您的死代码。动态语言有一个优点,即用通用委托者对象包装它们很简单,如果委托者从未用于转发消息,则该对象将在销毁时爆炸。 (2认同)