Introduction to RSpec - Part 3 - Testing the Rails Controller
This is the third part of the post about Introduction to RSpec. You can check the first post HERE and the second post here.
In this one, we are going to talk about the Spec::Rails project testing the Rails Controller.
We are keep going the same requirement than the second post, however now we’re going to focus on the Controller piece. Just to remember, the requirement is:
For our simple example, let’s create a save funcionality of an User. The user table has 3 fields: username, password and name. All of them are required and the username cannot repeat.
On the user interface, the user must enter the password twice. Both entries must be equals, otherwise an error message must be displayed.
If everything is ok, then the new user can be inserted into the database and a success screen must appear.
The first paragraph was already tested on the model layer. Now, we’re focus on in the second and third paragraph.
Before get started, let’s give a simple overview what we are going to do. Basically we are going to use three testing from RSpec::Controller: Response Expectations, Mock object and flash messaging. Of course there are others testing you can do, but for our example, we’re going to use those.
Creating the Controller
Like the model, we’re going to create the controller using the rspec generate script. To do that, simply type the following:
ruby script/generate rspec_controller user index add_user
We just created a controller (user) and two views (index and add_user). Four our testing, we’re focus on the add_user function.
Also, the rspec generate creates the test script into the directory spec/controllers. Open the spec/controllers/user_controller_spec.rb file to get started the testing.
As usual, the rspec already brings some coding.Those 3 methods are useful, so let’s keep them there.
Creating the Test Controller
Reading the requirement again carefully, we figured out the following required testing:
- If the password and repeat password aren’t equals, an error message should be displayed
- If either username or password or name is missing, an error message should be displayed
- If all values are correct, then the user can be insert into the database and a success message should be displayed.
Supposing our app/views/user/add_user.html.erb looks like:
<h2>Add new User</h2> <div id="error_message"> <% flash[:notice] if flash[:notice] %> <% error_messages_for :user %></div> <% form_for :user do |form| %> Username:<%= form.text_field :username %> Password:<%= form.password_field :password %> Repeat Password: <%= password_field_tag :repeated_password %> Name: <%= form.text_field :name %> <%= submit_tag "Submit" %> <% end %>
The result would be anything like below:
Creating the first test
Let’s get started with the first test, Password and Repeat Password must be equals. The method to test it would be anything like below:
describe "POST 'add_user'" do it "if #password and #repeated_password are not equals, should render the add_user.html.erb and display an error message" do post 'add_user',{:user => {:username => "jair", :password => "test123", :name => "Jair Rillo Junior"}, :repeated_password => "123test"} flash[:notice].should be_eql("Password and Repeat Password are wrong. Please re-type the password" ) response.should render_template('user/add_user' ) end end
Before run the rake spec:rcov, open the file spec/views/add_user.html.erb_spec.rb and delete the method before and it. We did it because we are not going to test the VIEW layer for time being.
Let’s spend some time looking at the code above. There is a call post. With this call, we can setup the parameters, in our example, a :user and a :repeated_password parameter.
Also, we’ve used the flash[:notice].should to test the return message and finally we’ve tested the render option (in our example, it rendered the add_user page again).
Now, run the rake spec:rcov to see the result.
1)
‘UserController POST ‘add_user’ if #password and #repeated_password are not equals, should render the add_user.html.erb and display an error message’ FAILED
expected eql?(”Password and Repeat Password are wrong. Please re-type the password”) to return true, got false
./spec/controllers/user_controller_spec.rb:27:Finished in 0.561393 seconds
11 examples, 1 failure
We’ve got an error!. It happed because the add_user has not been implemented yet. But before do it, let’s keep going our focus on the test script.
Creating the second test
The second test should test if ALL values are assigned, if not, the save function cannot be performed. Also, an error message should be displayed, however this test we’ve already tested into the model layer. We are going to test the following: if the save fails, the application render to the add_user properly. Here we’re going to use Mock object to mock the Model behavior. Remember, we’re doing unit testing, in other words, we are only testing the UNIT, no the whole system itself.
it "if either #username or #password or #name is missing, the save function cannot be performed" do user = mock(User) User.should_receive(:new).with({"password" => "test123","name" => "Jair Rillo Junior"}).and_return(user) user.should_receive(:password).and_return("test123" ) user.should_receive(:save).and_return(false) post 'add_user',{:user => {:password => "test123", :name => "Jair Rillo Junior"}, :repeated_password => "test123"} response.should render_template('user/add_user' ) end
Generally speaking, we must provide all actions that the object (in our case User object) will perform on the official method. In our case, the user object will be created (through NEW), then we will test the password field and finally we will save it.
Creating the third test
Finally let’s create the latest test. If all values are ok, insert the user into the database, redirect to the index action and display a success message. The code looks like:
it 'if all values are assigned, the user should be insert into the database, the index page should be opened and a success message should be displayed' do post 'add_user',{ :user => {:username => "jair", :password => "test123", :name => "Jair Rillo Junior" }, :repeated_password => "test123"} flash[:notice].should be_eql("User inserted successfully" ) response.should redirect_to(:action => :index) end
Hence, the full user_controller_spec.rb file is:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' ) describe UserController do #Delete these examples and add some real ones it "should use UserController" do controller.should be_an_instance_of(UserController) end describe "GET 'index'" do it "should be successful" do get 'index' response.should be_success end end describe "GET 'add_user'" do it "should be successful" do get 'add_user' response.should be_success end end describe "POST 'add_user'" do it "if #password and #repeated_password are not equals, should render the add_user.html.erb and display an error message" do post 'add_user',{ :user => { :username => "jair", :password => "test123", :name => "Jair Rillo Junior" }, :repeated_password => "123test"} flash[:notice].should be_eql("Password and Repeat Password are wrong. Please re-type the password" ) response.should render_template('user/add_user' ) end it "if either #username or #password or #name is missing, the save function cannot be performed" do user = mock(User) User.should_receive(:new).with({"password" => "test123","name" => "Jair Rillo Junior"}).and_return(user) user.should_receive(:password).and_return("test123" ) user.should_receive(:save).and_return(false) post 'add_user',{:user => {:password => "test123", :name => "Jair Rillo Junior"}, :repeated_password => "test123"} response.should render_template('user/add_user' ) end it 'if all values are assigned, the user should be insert into the database, the index page should be opened and a success message should be displayed' do post 'add_user',{ :user => {:username => "jair", :password => "test123", :name => "Jair Rillo Junior" }, :repeated_password => "test123"} flash[:notice].should be_eql("User inserted successfully" ) response.should redirect_to(:action => :index) end end end
If you run rake spec:rcov you will get the following:
1)
‘UserController POST ‘add_user’ if all values are assigned, the user should be insert into the database, the index page should be opened and a success message should be displayed’ FAILED
expected eql?(”User save successfully”) to return true, got false
./spec/controllers/user_controller_spec.rb:42:2)
‘UserController POST ‘add_user’ if either #username or #password or #name is missing, the save function cannot be performed’ FAILED
expected “actionadd_user”, got “user/add_user”
./spec/controllers/user_controller_spec.rb:35:3)
‘UserController POST ‘add_user’ if #password and #repeated_password are not equals, should render the add_user.html.erb and display an error message’ FAILED
expected eql?(”Password and Repeat Password are wrong. Please re-type the password”) to return true, got false
./spec/controllers/user_controller_spec.rb:27:Finished in 0.552346 seconds
13 examples, 3 failures
Implementing the add_user action method
Now we already have the rspec testing script done, let’s implement the add_user method. According to the requirement, the flow should be anything like this:
- If request is GET, open the add_user.html.erb file
- If request is POST, a new user should be created using the values from User Interface
- Test if the password and repeated password are equals, if not, display an error message
- If the password is correct, try to save it into database. If success, display a success message and redirect to index, otherwise open the add_usre.html.erb file
The code looks like:
class UserController < ApplicationController def index end def add_user if request.post? @user = User.new(params[:user]) if not @user.password.eql?(params[:repeated_password]) flash[:notice] = "Password and Repeat Password are wrong. Please re-type the password" render :action => :add_user else if @user.save flash[:notice] = "User inserted successfully" redirect_to :action => :index else render :action => :add_user end end end end end
Run the rake spec:rcov again and hopefully you will get the following:
………….
Finished in 0.614309 seconds
13 examples, 0 failures
Also, try out the rake stats.
Well, this post finishes here. It was an introduction to Rspec testing Rails Controller. The next one (and latest) we will talk about Testing the View.


Brazilian guy, IT Specialist, Linux User, IBM Certified SOA Fundamentals, Rational Developer, Sun Certified Java Associate 1.0, Sun Certified Java Programmer 1.4, Sun Certified Web Component Developer 1.4 and Sun Certified Business Component Developer 5. Also Ruby and Python enthusiastic.