RSpec allow/expect vs just expect/and_return
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
.
Comments
-
Paul Fioravanti almost 2 years
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 useallow
.However, in the (passing) sample code below, using either
allow
/expect
or justexpect
/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 overexpect
/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 toallow
/expect
. I really should have checked the most obvious place: the RSpec Mocks README, specifically the following sections: - Using