[rspec-users] Surprising mock behavior

Mark Thomson mark.thomson at ieee.org
Sat Oct 18 21:49:35 EDT 2008

Stephen Eley wrote:
> On Fri, Oct 17, 2008 at 2:55 PM, Mark Thomson <mark.thomson at ieee.org> wrote:
>> and then check that the expected messages are being received -
>> file.should_receive(:puts).with("a string").once
>> file.should_receive(:puts).with("another string").once
>> Here's what I'm puzzled about. If I don't include the expectation for the
>> first string in the spec, the spec will fail the expectation for the second
>> string. It seems as if "should_receive" is queuing up the messages that come
>> into the file object and when it tests an expectation it just looks at the
>> next one in line. If it doesn't match then the expectation will fail.
> That sounds right to me.  You declared 'file' as a mock.  Mocks are
> bratty little children that treat it as an error and throw a tantrum
> if you don't give them everything they expect, no more nor less.  (As
> contrasted to stubs, which are couch potatoes that will respond if you
> call them but don't complain if you don't.)
> So when you create a mock, you need to be very thorough about it.
> Every message has to be accounted for somehow.  If it gets more
> messages than you tell it, or different messages, it'll error.  If it
> doesn't get enough messages, it'll error.  This is correct behavior.

Thanks for the explanation Stephen. However, if that is the intention, 
I'm puzzled by something else - as I said the spec fails if I don't 
include an expectation for each output message. However it turns out 
that that's actually not always true. What I've observed is that it 
behaves differently if I include a "should_not_receive('...')" 
expectation somewhere in the spec. In that case it seems that I can have 
as many "file.puts()" in the component being tested as I like without 
specifying expectations for them, and they pass just fine. In fact I did 
have such a situation in my initial spec and I think that's what led me 
to my mistaken understanding of how should_receive is meant to work. But 
I'm struggling to understand what the rationale is that explains both of 
these cases.

That aside, I also can't help questioning the way the "should_receive" 
expectation is expressed. Maybe specifying every message sent to the 
mock is absolutely the right way to test the component. But in view of 
the general philosophy of expressing expectations in a way that reflects 
what they actually mean, in my mind this doesn't quite hit the mark. If 
you say "should receive", the way I read that is that if the object 
/does /receive what you specify then it should pass. But that's clearly 
not what happens. Nor is it  an expectation on what will be received 
next. If that were the case you might call the method 
"should_next_receive". However, in fact, as long as all messages are 
accounted for, you can reorder the individual "should_receive" 
expectations any way you like and the spec will still pass.

In fact "should_receive" does not appear to be an expectation on a 
single message at all (even if you say "should_receive().once", and 
leaving aside the exception with "should_not_receive" I noted above). I 
think a better way to think about this is that the total set of 
"should_receive" calls are _together_ an expectation on the totality of 
messages received by the object. In view of this, I wonder if a better 
way to formulate this test might be to say something like -

object.should_receive :method => :method_name, :with_each_of => [arg1, 
arg2,... argN]

where arg1, arg2 etc represent the parameters for each individual call 
to :method_name. i.e. declare the whole of what we _really_ expect the 
object to receive in a single call to should_receive.

You could take this one step further and declare all of the required 
calls to any number of methods on the object in a single expectation, by 
making the argument to should_receive an array -

object.should_receive [{:method => :method1, :with_each_of => [...]}, 
{:method => :method2, :with_each_of => [...]}, ...]

Yeah it gets a little wordy, but if I'm understanding the behavior 
correctly, this is what we are actually trying to test.

Does this make sense?


More information about the rspec-users mailing list