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

Matt Wynne matt at mattwynne.net
Sun Jun 28 17:04:39 EDT 2009


On 28 Jun 2009, at 13:07, Wincent Colaiuta wrote:

> 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).

I'd like to know more about how this happened. How did the model  
object's behaviour leak into the controller spec?

> 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.

So why not use

Comment.stub!(:find).with(123).and_return(mock(Comment))

> 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

cheers,
Matt Wynne

http://mattwynne.net
+447974 430184



More information about the rspec-users mailing list