In RSpec, specifically version >= 3, is there any difference between:

  • 使用allow设置消息期望值,参数返回test double,然后使用expect对返回的test double进行断言
  • 只需使用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

如果我故意将Expection中的passed in baz参数更改为另一个test 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)>'

那么,这两个测试在结果或表达的意图上有没有真正的区别,或者只是语义和/或个人偏好?一般来说,allow/expect应该在expect/and_return之上使用,因为它似乎是替换语法,还是它们都应该在特定的测试场景中使用?

Update

在阅读了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 double永远无法代表Foo.bar(baz)的结果,因此也从来没有调用过#qux
  • expect规格在Foo从未接收到.bar(baz)时失败,所以我们甚至没有到询问foobar_result双精度的时候

有道理:这不仅仅是语法上的改变,而且expect/and_return的目的与allow/expect不同.我真的应该判断最明显的地方:RSpec Mocks README,特别是以下部分:

推荐答案

参见classic 文章Mocks Aren't Stubs.allow制作一个存根,expect制作一个模拟.也就是说,allow允许一个对象返回X,而不是它将返回的任何未经subbed的对象,而expectallow plus,表示对某个状态或事件的期望.当你写作的时候

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

... 当spec环境收到:barbaz时,您告诉它修改Foo以返回foobar_result.但是当你写作的时候

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

... 你也在做同样的事情,并且告诉规范失败unless Foo接收:barbaz.

要想了解两者的区别,可以在Foobaz的例子中都try 一下.

Ruby相关问答推荐

每次调用返回新的 REST 响应的 Ruby Rspec class_double

当数据包含换行符时,Ruby PKCS7 无法验证

使用 Ruby,我如何访问和比较这些嵌套的数组值?

使用数组作为其中之一转换键和值

查看 Savon 请求 XML 而不发送到服务器

在 Ruby 中覆盖 == 运算符

用mustache迭代数组

在文件中搜索字符串的最佳方法是什么?

可可豆荚安装在iOS项目上不起​​作用

如何删除字符串中的重复空格?

线程安全:Ruby 中的类变量

覆盖子类中的 ruby​​ 常量,以便继承的方法使用新常量而不是旧常量?

在 Ruby 中将散列扁平化为字符串

条件子句中的赋值是好的Ruby风格吗?

Xcode - 配置:错误:在 $PATH 中找不到可接受的 C 编译器

Ruby:define_method 与 def

Array.empty 的相反方法是什么?或 [].empty?在Ruby中

如何将 270921sec 转换为天 + 小时 + 分钟 + 秒? (Ruby)

Eclipse 的首选 Ruby 插件?

从 'mm/dd/yyyy' 格式解析 ruby​​ DateTime