On 28 Jun 2009, at 23:02, Wincent Colaiuta wrote:
El 28/6/2009, a las 23:04, Matt Wynne escribió:
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?
This was a spec for the controller's "update" action, which does a
"save" on the record. At one point a change was made to the model to
do some complex updates in the after_save callback, and these
involved doing another Comment.find call, but with different
parameters.
If I understand this correctly, there was only one call from
Controller -> Comment that you wanted to test; the other one was a
call from Comment -> Comment that happened as a side-effect.
So I'm wondering: if you'd returned a fake (mock, stub, whatever)
comment from your stubbed Comment.find, would that have solved the
problem?
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))
Because there are actually two "find" calls here:
- the one I actually care about
- the other one in the after_save callback which is irrelevant to
the controller
I original used "should_receive", not "stub", so RSpec complained
about getting "find" with the unexpected parameters. If I change to
"stub" then I'm losing my assertion (no longer checking that the
message gets sent), injecting a different return value (adding
complexity), for no visible benefit (may as well just throw away the
expectation).
What I often do is put a stub in first, which will work in all the
examples, then put a should_receive in one of the examples if (as
seems to be the case here) it's important to me to test the
collaboration between the objects. So it would look like this:
describe "#update" do
before(:each)
@comment = mock(Comment)
Comment.stub!(:find).and_return(@comment)
end
it "should call the model to try and find the comment"
Comment.should_receive(:find).with(123).and_return(@comment)
do_request
end
it "should assign the comment to the view"
do_request
assigns[:comment].should == @comment
end
So the stub works in the background, then when you want to actually
assert for the collaboration, you can override it with a
should_receive. I find this pattern works really well for me.
cheers,
Matt Wynne
http://mattwynne.net
+447974 430184
_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users