Testing using RSpec, Part 2 - the Controller
November 04, 2008 at 03:17 PM · Posted under Blog · Tagged with controllers, rails, rspec, ruby, testing, unit
In order to make much sense of this, I'm going to do this article over three stages. The first stage will go over the controller and some simple methods one might find in a controller. The second stage will highlight some of the key aspects of testing our controllers and finally, the third stage - will see the article come together in a blur of RSpec, Controller and passing tests.
To start with, let's create a controller that will handle the various actions a user might do when handling their own account. I'm going to keep this controller fairly simple, as I want to focus on the testing, not the code itself, so please bear in mind that when writing your own controllers to ensure you follow the appropriate procedures to make your application secure and stable.
class UsersController < ApplicationController
def login( username, password )
user = User.login( username, password )
unless user.blank?
session[:user] = user.id
end
end
end
When writing tests for our controllers, we want to ensure that our tests are as isolated as possible. Why? We only want to test the code we're working with and if we can, leave any dependencies of our controller outside in the cold. Reason for this is so that should we encounter any problems, either with our code or our tests - we know exactly where the problem is and how to fix it. As soon as you start introducing tests for code from outside the controller, you then begin to see an example of ambiguity as you now have to debug the code further to find the exact problem - and this should have been picked up in other tests, either in your models, or 3rd party libraries.etc. Add to this the fact that your tests get messy, and it's easy to see why this sort of "behaviour" is shunned against. Let's begin with some tests for the login method.
describe UsersController do
describe :login do
before :each do
@params = {:username => 'username', :password => 'password'}
end
it "should not log the user in if no user was found" do
User.should_receive(:login).with(params[:username], params[:password]).and_return(false)
post :login, params
session[:user].should be_nil
end
end
end
What we're doing is is firstly, stubbing out the login method on the user model (this could be anything you like, just make sure you stub out the correct method), but ensuring it receives the correct parameters and forcing it to return false. This in turn will unless user.blank? check fail, thereby not setting any session variable, which we check for with session[:user].should be_nil.
Now, if we're going to look at our coverage, you'll note that we've got some code that never gets executed with this test, so we'll have to write another. Let's extend this test with another one and make sure that code gets executed:
it "should log the user in if the details are correct" do
User.should_receive(:login).with(params[:username], params[:password]).and_return(1)
post :login, params
session[:user].should eql(1)
end
As you can see here, we've changed the stub of the login method a little differently, ensuring that it responds with a numeric value, the user id. We then assert that the user key for the session array is equal to 1, and our test passes! Yay!
This is a fairly basic login method, so let's update this a little further with something a little more complex. First, let's update our test for the successful login:
it "should log the user in if the details are correct" do
User.should_receive(:login).with(params[:username], params[:password]).and_return(1)
post :login, params
session[:user].should eql(1)
response.should redirect_to(:controller => 'home', :action => 'index')
end
You can see here that we're now checking for some redirect functionality, which doesn't exist in our code - meaning our test will fail! So now we can go and update our code to ensure that the intended functionality is in place, and watch our test pass:
class UsersController < ApplicationController
def login( username, password )
user = User.login( username, password )
unless user.blank?
session[:user] = user.id
redirect_to( { :controller => 'home', :action => 'index' } )
end
end
end
And there you have it! Test-driven development at it's very primitive finest! We've written some basic login code, and written tests to ensure that all cases against that method are working as intended, whilst isolating our tests to just that method. In the next article, we're going to look at how we can test our views to ensure the expected content is being rendered to the browser.
Permalink