In RSpec, specifically version >= 3, is there any difference between:
- Using
allow
to set up message expectations with parameters that return test doubles, and then usingexpect
to make an assertion on the returned test doubles - Just using
expect
to set up the expectation with parameters and return the test double
or 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)>'
- The
allow
spec fails because thefoobar_result
double never gets to stand in for the result ofFoo.bar(baz)
, and hence never has#qux
called on it - The
expect
spec fails at the point ofFoo
never receiving.bar(baz)
so we don't even get to the point of interrogating thefoobar_result
double
Makes 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:
Best Answer
See the classic article Mocks Aren't Stubs.
allow
makes a stub whileexpect
makes a mock. That isallow
allows an object to return X instead of whatever it would return unstubbed, andexpect
is anallow
plus an expectation of some state or event. When you write... you're telling the spec environment to modify
Foo
to returnfoobar_result
when it receives:bar
withbaz
. But when you write... you're doing the same, plus telling the spec to fail unless
Foo
receives:bar
withbaz
.To see the difference, try both in examples where
Foo
does not receive:bar
withbaz
.