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:
Thinking about switching to RR · wincent.com
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:
- That Comment.find gets called with a specific param at some
point in time. - 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