RSpec - Mocks and Stubs

Behavior Driven Development (BDD) has become a driving force in the development industry right along the process of agile development. The two are almost synonymous with each other if you have done any sort of Agile/XP/SCRUM process on any of your projects. In the ruby world and the surrounding communities, RSpec has evolved as the leader for applying BDD practices and I felt it was about time I mastered the BDD process for all my projects.

The beauty of RSpec is in its simplicity. The code is very readable by anyone who comes across of it. For me, the syntax was the easy part. The headaches began once I tried to wrap my head around mocks (mocking) and stubs. Stubs seemed easier to start with, so I hit Google confident that my answer was just clicks away.

Well, I was wrong… After spending about an hour researching stubs, I figured maybe I should look up mocks. Unfortunately though, I got the same result posted at over 20 blogs. It was a link to a site called mockobjects. Now I am sure that somewhere on that site I could piece together an understanding of mocks and stubs, but I already had wasted a couple hours and did not have a day to spend on picking up these, seemingly simple, concepts.

Finally a ray of hope came when someone over at RailsForum posted the question I had been asking. The answers, although similar to ones I had read, made it clear as mud enough for me to take a stab at trying out some code. This is what I ended up with:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe UsersController, 'working with the show action' do

before(:each) do
@user = mock_model(User)
User.stub!(:find).and_return(@user)
end

it "should call the action successfully" do
get :show, :id => "1"
response.should be_success
end

it "should find the user" do
User.should_receive(:find).with("1").and_return(@user)
get :show, :id => "1"
end

it "should assign the found user to the view" do
get :show, :id => "1"
assigns[:user].should eql(@user)
end

end

describe UsersController, 'working with the index action' do
before(:each) do
@user = mock_model(User)
@users = [@user]
User.stub!(:find).and_return(@users)
end

it "should call the action successfully" do
get :index
response.should be_success
end

it 'should find the users' do
User.should_receive(:all).and_return(@user)
get :index
end

it 'should assign the found users to the view' do
get :index
assigns[:users].should eql(@users)
end
end

describe UsersController, 'working with the new action' do

end

describe UsersController, 'working with the create action' do

end

describe UsersController, 'working with the destroy action' do
before(:each) do
@user = mock_model(User)
User.stub!(:find).and_return(@user)
@user.stub!(:destroy).and_return(true)
end

it 'should find the user' do
User.should_receive(:find).with('1').and_return(@user)
delete :destroy, :id => 1
end

it 'should destroy the user' do
@user.should_receive(:destroy).once
delete :destroy, :id => 1
end

it 'should redirect to index' do
delete :destroy, :id => 1
response.should redirect_to(users_path)
end
end

Now I know I should have started with snippet by snippet and build up to a big post. However I feel its much easier to post the end result then break it down as we go, and its much quicker :)

Stubs: Simply put, it allows you to build canned responses to method calls. The reason for doing this is to maintain fast test execution by removing any database calls. Another factor is your controller tests (also known as functional tests) don’t need to rely on your models. Now when something breaks you don’t need to troubleshoot where to look since the model and controller are tested separately.

Mocks: Unlike stubs, I think of mocks as a basic barebone instance of an object. If you were to just define

@user = mock(’User’)

The @user object would respond to anything defined in your model. This means there is coupling between controller and model, unlike stubs, meaning when your tests break you need to do slightly more debugging. The amount of debugging can be minimized through good design.

To be continued.


Leave a Reply