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

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to