[rspec-users] When to use Factories, Mock Models, Mocks & Stubs

J. B. Rainsberger jbrainsberger at gmail.com
Wed Feb 3 06:35:32 EST 2010


On Tue, Feb 2, 2010 at 19:00, Frank Lakatos <me at franklakatos.com> wrote:

> Hi guys, been following for about 3 weeks, first question -

This might help a little: http://bit.ly/ONpXE

To bring things back to Rails, I use mock_model whenever I want to
design controller behavior without relying on the underlying model
behavior. I tend to start using mock_model and mostly stubbing model
behavior, then as controller behavior begins to reveal itself as model
behavior, I push that into the model and mock those methods more
frequently.

I find this rule of thumb helpful: stub unless you're certain to want
to verify this time that the client invoke the server correctly, and
never, never mock multiple methods at once. If you want to mock
multiple methods, you probably have too complex an interaction.

> app/controller/projects_controller#create
>
>  def create
>      @client =
> current_user.company.clients.find(params[:project][:client_id])
>      @project = @client.projects.build(params[:project])
>      if @client.save
>        flash[:notice] = "Added: #{@project.name}"
>      else
>        render :new
>      end
>  end
>
> spec/controller/projects_controller_spec
>
> describe ProjectsController do
>    describe "POST 'create'" do
>
>    before do
>      @current_user = mock_model(User)
>      controller.stub(:current_user).and_return @current_user
>      @company = mock_model(Company)
>      @current_user.should_receive(:company).and_return @company
>      @clients = mock("Client List")
>        @company.should_receive(:clients).and_return @clients
>    end
>
>    describe "when client is found" do
>
>      before do
>        @client = mock_model(Client)
>        @clients.should_receive(:find).and_return @client
>      end
>
>      describe "on successful save" do
>
>        before do
>          @projects = mock_model(ActiveRecord)
>          @client.should_receive(:projects).and_return @projects
>          @project = mock_model(Project)
>          @projects.should_receive(:build).and_return @project
>          @client.should_receive(:save).and_return true
>          @project.should_receive(:name).and_return "New Project"
>        end
>
>        it "should set up the flash" do
>          post "create", {:project => {:client_id => 1}}
>          flash[:notice].should_not be_nil
>        end
>
>       end
>
>    end
>
>  end
>
>
> end
>
>
> Let me know how it sounds, and it looks like I'm doing so far

Not bad, but I'd probably extract a method for

current_user.company.clients.find(params[:project][:client_id])

and possibly for

@client.projects.build(params[:project])

in order to reduce the number of details that have to go into a single spec.

def find_client_for_current_user(client_id)
    current_user.company.clients.find(client_id)
end

def build_new_project(client, project_attributes)
    client.projects.build(project_attributes)
end

def create
    @client = find_client_for_current_user(params[:project][:client_id])
    @project = build_new_project(@client, params[:project])
    if @client.save
        flash[:notice] = "Added: #{@project.name}"
    else
        render :new
    end
end

Now I can write these specs:

stub each of the methods in the first three columns...
find_client | build_project | save || expected_result
valid | valid | true || added project
nil | valid | shouldn't happen || exception (?)
valid | fails | shouldn't happen || exception (?)
valid | valid | false || errors; render new

and finally:

1. stub :find_client to answer mock_model(Client); controller should
receive :build_new_project with the mock model
2. stub :find_client to answer nil; controller should not receive
:build_new_project
3. stub :find_client to answer mock_model(Client); mock model should
receive :save

That's the initial spec list I'd write.
-- 
J. B. (Joe) Rainsberger :: http://www.jbrains.ca ::
http://blog.thecodewhisperer.com
Diaspar Software Services :: http://www.diasparsoftware.com
Author, JUnit Recipes
2005 Gordon Pask Award for contribution to Agile practice :: Agile
2010: Learn. Practice. Explore.


More information about the rspec-users mailing list