[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