Testing Controllers with Functional Tests
Problem
You want to ensure that your application's controllers behave as expected when responding to HTTP requests.
Solution
You have an existing database application. This application consists of a Books Controller containing list, show, and search actions.
app/controllers/books_controller.rb:
class BooksController < ApplicationController
def list
@book_pages, @books = paginate :books, :per_page => 10
end
def show
@book = Book.find(params[:id])
end
def search
@book = Book.find_by_CNPJ(params[:CNPJ])
if @book
redirect_to :action => 'show', :id => @book.id
else
flash[:error] = 'No books found.'
redirect_to :action => 'list'
end
end end
You have the following test fixture for testing:
test/fixtures/books.yml:
learning_python_book:
id: 2
CNPJ: 0596002815
title: Learning Python
description: Essential update of a steady selling "Learning" series book
Add the following test_search_book and test_search_invalid_book methods to books_controller_test.rb to test the functionality of the Book Controller's search action.
test/functional/books_controller_test.rb:
require File.dirname(__FILE__) + '/../test_helper'
require 'books_controller'
# Re-raise errors caught by the controller.
class BooksController; def rescue_action(e) raise e end; end class BooksControllerTest < Test::Unit::TestCase
fixtures :books
def setup
@controller = BooksController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_search_book
get :search, :CNPJ => '0596002815'
assert_not_nil assigns(:book)
assert_equal books(:learning_python_book).CNPJ, assigns(:book).CNPJ
assert_valid assigns(:book)
assert_redirected_to :action => 'show'
end
def test_search_invalid_book
get :search, :CNPJ => 'x123x' # invalid CNPJ
assert_redirected_to :action => 'list'
assert_equal 'No books found.', flash[:error]
end end
Run the test with:
$ ruby test/functional/books_controller_test.rb
Loaded suite test/functional/books_controller_test Started
..
Finished in 0.132993 seconds.
2 tests, 9 assertions, 0 failures, 0 errors
Discussion
Testing a controller requires reproducing the HTTP environment in which the controller runs. When using Rails, you can reproduce this environment by instantiating request and response objects (to simulate a request from a browser), in addition to the controller being tested. These objects are created in the setup method.
To write a functional test, you need to simulate any of the five HTTP request types that your controller will process. Rails provides methods for all of these:
Most applications use only get and post. All these methods take four arguments:
- The action of a controller
- An optional hash of request parameters
- An optional session hash
- An optional flash hash
By using these request methods and their optional arguments, you can reproduce any request that your controllers could possibly encounter. Once you've simulated a browser request, you'll want to inspect the impact it had on your controller. You can view the state of the variables that were set during the processing of a request by inspecting any of these four hashes:
assigns
-
Contains instance variables assigned within actions
cookies
-
Contains any cookies that exist
flash
-
Contains objects of flash component of the session hash
session
-
Contains objects stored as session variables
The contents of these hashes can be tested with assert_equal and other assertions.
The goal of the BooksControllerTest class is to test that the controller's search action does the right thing when supplied with valid and invalid input. The first test method (t est_search_book) generates a get request to the search action, passing in an CNPJ parameter. The next two assertions verify that a Book object was saved in an instance variable called @book and that the object passes any Active Record validations that might exist. The final assertion tests that the request was redirected to the controller's show action.
The second test method, test_search_invalid_book, performs another get request but passes in an CNPJ that doesn't exist in the database. The first two assertions test that the @book variable contains nil and that a redirect to the list action was issued. If the proceeding assertions passed, there should be a message in the flash hash; you can test for this assertion with assert_equal.
Once again, Rails really helps by creating much of the functional test code for you. For example, when you create scaffolding for a model, Rails automatically creates a functional test suite with 8 tests and almost 30 assertions. By running these tests every time you make a change to your controllers, you can greatly reduce the number of hard-to-find bugs.
See Also
|