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

David Chelimsky dchelimsky at gmail.com
Mon Nov 27 07:35:32 EST 2006


On 11/27/06, Pat Maddox <pergesu at gmail.com> wrote:
> On 11/26/06, David Chelimsky <dchelimsky at gmail.com> wrote:
> > This could be solved by
> > adding an add_person method to User. Now your controller spec could
> > look like this:
> >
> > specify "should create a new Person and stick it in the users people" do
> >   mock_user = mock("user")
> >   mock_person = mock("person")
> >   mock_user.should_receive(:add_person).with(mock_person)
> >
> >   User.should_receive(:find_by_login).with("pat").and_return(mock_user)
> >   Person.should_receive(:new).with(:name => "BJ").and_return(mock_person)
> >
> >   post :create, :user_id => "pat", :person => { :name => "BJ" }
> > end
>
> If I want to specify the redirect, would I do
>
>   specify "should redirect to the new person" do
>     mock_user = mock("user", :null_object => true)
>     mock_person = mock("person")
>
>     mock_user.should_receive(:to_param).and_return("pat")
>     mock_person.should_receive(:to_param).and_return(1)
>
>     User.should_receive(:find_by_login).with("pat").and_return(mock_user)
>     Person.should_receive(:new).with("name" => "BJ").and_return(mock_person)
>
>     controller.should_redirect_to :action => "show", :user_id => "pat",
>                                   :id => 1
>     post :create, :user_id => "pat", :person => { :name => "BJ" }
>   end
>
> or should I just include the controller.should_redirect_to in the
> first spec I showed?  I try to stick to Dave Astels' "One Expectation
> Per Example" guideline...and I usually do fine when writing model
> code, or standalone classes, but obviously neither of these examples
> follows that at all closely.  Is it even possible to follow it with
> controller specs?  I'm trying to get some heuristics for determining
> how much is too much in a single spec.
>
> In my example, redirecting behavior doesn't have anything to do with
> the behavior of adding a user, so I think they should be split out.
> But then there's a lot of code in the second spec that has nothing to
> do with the redirect behavior (User.find and Person.new certainly
> don't have anything to do with that).  Is that just a consequence of
> all that goes on in an action, or is it a sign that there's room to
> refactor?

Here's how I've been handling this sort of thing. Bear in mind that I
am a big fan of either "One Expectation Per Example", for which I make
exceptions when the expectations are very cohesive. In this case, I
think they are sufficiently un-cohesive to warrant their own specs.

context "Given a post to :create, the UserController should" do

  # Use stubs in setup just to get things to run - this is just
  # noise, but necessary noise, and less noisy when in setup that
  # in individual specs.
  setup do
    @mock_user = mock("user")
    @mock_person = mock("person")
    @mock_user.stub!(:to_param).and_return("pat")
    @mock_user.stub!(:add_person)
    @mock_person.stub!(:to_param).and_return(1)
    User.stub!(:find_by_login).and_return(@mock_user)
    Person.stub!(:new).and_return(@mock_person)
  end

  def do_post
    post :create, :user_id => "pat", :person => { :name => "BJ" }
  end

  # Use mock expectations (should_receive) in the specify blocks. Even
  # though they are redundant with the stubs in setup, they help tell
  # the story of this particular spec.
  specify "find the relevant User" do
    User.should_receive(:find_by_login).with("pat").and_return(@mock_user)
    do_post
  end

  specify "create a new Person" do
    Person.should_receive(:new).with(:name => "BJ").and_return(@mock_person)
    do_post
  end

  specify "add the new Person to the User" do
    @mock_user.should_receive(:add_person).with(@mock_person)
    do_post
  end

  #example using controller expectations
  specify "redirect to the new Person" do
    controller.should_redirect_to :action => "show", :user_id => "pat", :id => 1
    do_post
  end

  #example using response expectations
  specify "redirect to the new Person" do
    do_post
    response.redirect_url.should_eql "http://test.host/users/show/1"
  end
end

This approach allows you to separate concerns in different specs while
minimizing noise within each spec. Looking at setup tells you everything
that has to happen just to get things to run, and can often reveal code
smells that you might not otherwise see. Also, the output is quite nice:

Given a post to :create, the UserController should
- find the relevant User
- create a new Person
- add the new Person to the User
- redirect to the new Person

Looking at that is helpful too. Perhaps this would be fine:

Given a post to :create, the UserController should
- find the relevant User
- create a new Person and add it to the User
- redirect to the new Person

or even this:

Given a post to :create, the UserController should
- find the relevant User, create a new Person and add it to the User
- redirect to the new Person

Looking at it this way, the specs that I've combined do
seem cohesive so I'd be comfortable with this or with
each one separated out.

Hope this is all helpful.

Cheers,
David

>
> Pat
> _______________________________________________
> rspec-devel mailing list
> rspec-devel at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-devel
>


More information about the rspec-devel mailing list