[rspec-devel] Fwd: [ rspec-Bugs-6905 ] Mocking rails associations where the association is validated

Pat Maddox pergesu at gmail.com
Mon Nov 27 15:56:41 EST 2006


On 11/27/06, David Chelimsky <dchelimsky at gmail.com> wrote:
> On 11/27/06, Pat Maddox <pergesu at gmail.com> wrote:
> > Another question :)
> >
> > When I first wrote the add_person method, my spec looked like this:
> >
> >   specify "should accept a new person" do
> >     @user.people.size.should_be 0
> >     @user.add_person Person.new
> >     @user.people.size.should_be 1
> >   end
> >
> > That works, of course, and lets me infer that I have the new user.
> > However I think it's ugly that I'm accessing people to verify
> > something about the user...
> >
> > So then I decided to change it to this:
> >
> >   specify "should accept a new person" do
> >     mock_person = mock("person")
> >     mock_people = mock("people")
> >     @user.should_receive(:people).and_return(mock_people)
> >     mock_people.should_receive(:<<).with(mock_person)
> >     @user.add_person mock_person
> >   end
> >
> > That feels like the more RSpecy way to do this, despite being longer.
> > In both cases they dig into people (either checking the size or
> > checking that it calls <<), but in the latter case it verifies
> > behavior, rather than inferring it from state.  Does that sound right?
>
> This one is tricky. There are multiple perspectives on how much
> mocking you should do in model specs. One view is that model specs are
> really small integration specs (model class + database). Another view
> is that you should do what you did above, mocking out the other
> players where possible.
>
> I'm leaning towards the first view - that model specs are like little
> integration specs. My thinking is that because the way AR is designed,
> the things you end up mocking are often internal to AR, which is a
> huge mocking no-no (mock YOUR code, not other people's).

Weren't we mocking other people's code in the controller specs as well?
ARClass.should_receive(:find).with(1).and_return(some_mock_object)
ARClass.should_receive(:new).with(:name => "name").and_return(other_mock_object)

I'm not really sure what the difference is between mocking that out
and mocking players.  At first I thought that it's because the find
and new calls appear in my code, but so does players <<.


> In this case you could use this to make it feel more RSpecy without
> mocking anything:
>
> specify "should accept a new person" do
>   lambda do
>     @user.add_person
>   end.should_change(@user.people.size).by(1)
> end
>
> I'm not a huge fan of going after @user.people.size here, but I think
> that @user.number_of_people might be overkill.

After thinking about this some more, I think I like the other version
with lots of mocks.  To me, it boils down to what I think is a
revelation I had last night...using the specs do define behavior,
rather than infer it from state.

The one problem I see with my example was what if the User object
doesn't define a players method?  Then the spec passes fine, but the
code blows up of course.  This would be easily solved if you ensure
that partial mocks can only mock methods that the object already has.

for example, I've got a user object that has players in this case...when I do
user.should_receive(:players)
then I think the should_receive call out to call
user.respond_to?(players) before mocking the method.  That seems
reasonable to me for partial mocks - we're just substituting in some
code for behavior that should already exist, because the existing code
is too complex or expensive and doesn't need to be executed at the
moment.  At the very least, perhaps it could generate a warning such
as "WARNING - 'people' not defined on user object."  WDYT?

Pat


More information about the rspec-devel mailing list