In RSpec, specifically version >= 3, is there any difference between:
allow to set up message expectations with parameters that return test doubles, and then using expect to make an assertion on the returned test doublesexpect to set up the expectation with parameters and return the test doubleor is it all just semantics? I know that providing/specifying a return value with expect was the syntax in RSpec mocks 2.13, but as far as I can see, the syntax changed in RSpec mocks 3 to use allow.
However, in the (passing) sample code below, using either allow/expect or just expect/and_return seems to generate the same result. If one syntax was favoured over another, perhaps I would have expected there to be some kind of deprecation notice, but since there isn't, it would seem that both syntaxes are considered valid:
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
If I deliberately make the tests fail by changing the passed-in baz parameter in the expectation to a different test double, the errors are pretty much the same:
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)>'
So, are there any real differences between these two tests, either in result or expressed intent, or is it just semantics and/or personal preference? Should allow/expect be used over expect/and_return in general as it seems like it's the replacement syntax, or are each of them meant to be used in specific test scenarios?
Update
After reading Mori's answer's, I commented out the Foo.bar(baz).qux line from the example code above, and got the following errors:
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 spec fails because the foobar_result double never gets to stand in for the result of Foo.bar(baz), and hence never has #qux called on itexpect spec fails at the point of Foo never receiving .bar(baz) so we don't even get to the point of interrogating the foobar_result doubleMakes sense: it's not just a syntax change, and that expect/and_return does have a purpose different to allow/expect. I really should have checked the most obvious place: the RSpec Mocks README, specifically the following sections:
See the classic article Mocks Aren't Stubs. allow makes a stub while expect makes a mock. That is allow allows an object to return X instead of whatever it would return unstubbed, and expect is an allow plus an expectation of some state or event. When you write
allow(Foo).to receive(:bar).with(baz).and_return(foobar_result)
... you're telling the spec environment to modify Foo to return foobar_result when it receives :bar with baz. But when you write
expect(Foo).to receive(:bar).with(baz).and_return(foobar_result)
... you're doing the same, plus telling the spec to fail unless Foo receives :bar with baz.
To see the difference, try both in examples where Foo does not receive :bar with baz.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With