[rspec-users] Test doubles: expect "x" and don't care about anything else

Wincent Colaiuta win at wincent.com
Sun Jun 28 08:07:28 EDT 2009


I've had one of my recurring doubts about test doubles come up again.

The full post is here but I'll abbreviate the content in this message  
in any case:

   https://wincent.com/blog/thinking-about-switching-to-rr

Basically, in one of my controller specs I wanted to verify that the  
following line was being called and doing the right thing:
   @comment = Comment.find params[:id]
I had a mock for this set up, but it broke when unrelated code in the  
model was modified (a complex callback which itself called  
Comment.find).

The problems were as follows:

   - A mock was more than I really needed, as I didn't want to go  
through the complication of returning a substitute object.

   - The expectation set on the mock was too strict, because the other  
message send to Comment.find, the one I didn't care about, was  
triggering a failure.

   - A proxy would suffice, because all I really wanted to confirm was  
that the "find" message was sent, without actually interfering with  
the returned object.

   - I basically wanted to set an expectation "that this class will  
receive this message with these params", but the frameworks didn't  
allow me to do that because in reality you can only assert "that this  
class will receive this message with these params _and not receive  
that message with any other params at any time_"

In my blog post I detailed the possible options for avoiding the  
problem, and the easiest ended up being: forget mocks and proxies  
entirely and instead test the side-effect (that the expected object  
ends up getting assigned to the "@comment" instance variable).

So the workaround worked, but RSpec's own mock framework, and from  
what I can tell, the alternatives such as Mocha, RR et al, wouldn't  
really let me make the kind of assertion that I wanted to make: ie.  
"confirm this message gets sent at some point, but don't modify the  
behaviour at all, and don't interfere with or worry about any other  
messages that get sent to the object, including messages sent to the  
method that I'm setting the expectation on".

In my ideal test-double framework, I'd like to really assert two  
things about the line of code in question:

   1. That Comment.find gets called with a specific param at some  
point in time.
   2. That the @comment instance variable gets the expected value  
assigned to it.

I literally don't care about what other messages get sent to Comment,  
nor about other places in the code where Comment.find might get called  
with other parameters, and in any case I don't want to actually modify  
the behaviour or substitute my own return values. But it seems I can't  
do this with existing test double frameworks, and it makes it hard to  
write minimal specs with one expectation and as few test doubles as  
possible (ideally zero or one) per "it" block.

Ideally I'd want to write something like:

   it 'should find the comment' do
     proxy(Comment).find(@comment.id.to_s)
     do_put
   end

   it 'should assign to the @comment instance variable' do
     assigns[:comment].should == @comment
     do_put
   end

Note that with the "proxy" syntax above I'm trying to say:

   - I expect this message and these params to be sent at some point

   - I don't care if other messages are sent

   - I don't even care if the "find" method is also called with  
different params

   - I don't care about the order of the messages

   - I don't want to interfere with or substitute the return value

I don't know whether the syntax is adequate, or whether some keyword  
other than "proxy" would be required. Another alternative I thought of  
was:

   it 'should find the comment' do
     spy(Comment)
     do_put
     Comment.should have_received.find(@comment.id.to_s)
   end

Or similar... Basically saying that I want the double framework to spy  
(proxy _and_ record) all messages to the specified receiver, and that  
afterwards I'm going to retrospectively check that among the recorded  
messages is the one I'm looking for.

What do other people think?

   - is what I'm wanting to do a reasonable approach?

   - are there any test double frameworks out there which would allow  
me to work in this way?

Cheers,
Wincent



More information about the rspec-users mailing list