Подтвердить что ты не робот

RSpec позволяет/ожидать, а просто ожидать/and_return

В RSpec, в частности версии >= 3, есть ли разница между:

  • Используя allow, чтобы настроить ожидания сообщений с параметрами, возвращающими тестовые двойники, а затем с помощью expect, чтобы сделать утверждение по возвращенному тесту удваивается
  • Просто используя expect, чтобы настроить математическое ожидание с параметрами и вернуть двойной тест

или все это просто семантика? Я знаю, что предоставление/указание возвращаемого значения с помощью expect было синтаксис в RSpec mocks 2.13, но, насколько я вижу, синтаксис, измененный в 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

Если я намеренно сделаю тесты неудачными, изменив параметр pass-in 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 я прокомментировал строку 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 double

Имеет смысл: это не просто изменение синтаксиса, а expect/and_return имеет цель, отличающуюся от allow/expect. Я действительно должен был проверить наиболее очевидное место: RSpec Mocks README, в частности следующие разделы:

4b9b3361

Ответ 1

См. классическую статью Mocks Are not Stubs. allow делает заглушку, а expect делает макет. То есть allow позволяет объекту возвращать X вместо того, что он будет возвращать ненагруженным, а expect - это allow плюс ожидание какого-либо состояния или события. Когда вы пишете

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

... вы сообщаете среде спецификации изменить Foo, чтобы вернуть foobar_result, когда он получает :bar с baz. Но когда вы пишете

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

... вы делаете то же самое, плюс говорите, что спецификация терпит неудачу, если Foo не получит :bar с baz.

Чтобы увидеть разницу, попробуйте оба примера, где Foo не получает :bar с baz.