Ruby on Rails 2.1 - Applications' Unit Testing
Introduction:
Before proceeding let's have a quick look of few definitions:
The Tests - They are test applications that produce consistent result and prove that a Rails application does what it is expected to do. Tests are developed concurrently with the actual application.
The Assertion - This is a one line of code that evaluates an object (or expression) for expected results. For example, is this value = that value? is this object nil?
The Test Case - This is a class inherited from Test::Unit::TestCase containing a testing strategy comprised of contextually related tests.
The Test Suite - This is a collection of test cases. When you run a test suite, it will, in turn, execute each test that belongs to it.
Rails Testing:
When you run the helper script script/generate to create controllers and models, Rails generate a framework for unit and functional tests. You can get pretty good test coverage by filling in the framework with tests for the functionality you write. There are two important points to test in a Rails application:
Testing the Models.
Testing the Controllers.
This tutorial will cover both the testings in brief. So let's create one testapp to understand the concept.
C:\ruby> rails -d mysql testapp
|
Database Setup:
Till now we have used only Rails application's development database, but now you needed to make sure that the testing database is also created and appropriate section of your config/database.yml file was set up correctly.
Let's create development and testing databases as follows:
mysql> create database testapp_test;
Query OK, 1 row affected (0.01 sec)
mysql> create database testapp_development;
Query OK, 1 row affected (0.01 sec)
mysql> use testapp_test;
Database changed
mysql> grant all privileges on testapp_test.*
to 'root'@'localhost' identified by 'password';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
|
Configuring database.yml:
Configure your config/database.yml as follows:
development:
adapter: mysql
encoding: utf8
database: testapp_development
username: root
password: password
host: localhost
test:
adapter: mysql
encoding: utf8
database: testapp_test
username: root
password: password
host: localhost
production:
adapter: mysql
encoding: utf8
database: testapp_production
username: root
password: password
host: localhost
|
Generate Migration:
Assume you have a table containing books, including their titles, price and a small description. The following migration sets up this table:
testapp > ruby script/generate migration books
|
Now modify the testapp/db/migrate/20080616170315_books.rb file as follows:
class Books < ActiveRecord::Migration
def self.up
create_table :books do |t|
t.string :title, :limit => 32, :null => false
t.float :price
t.text :description
t.timestamp :created_at
end
end
def self.down
drop_table :books
end
end
|
Now run the migration as follows:
testapp > rake db:migrate
|
This will create books table in testapp_development database. Now after this we need to set up your test database using rake command as follows:
C:\ruby\testapp > rake db:test:clone_structure
|
This will clone testapp_development database into testapp_test database. It means whatever you have in development database, now you will have in test database as well.
Testing Models:
When you generate a model with the generate script, Rails also generates a unit test script for the model in the test directory. It also creates a fixture, a YAML file containing test data to be loaded into the testapp_test database. This is the data against which your unit tests will run:
testapp > ruby script/generate model Book
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/book.rb
create test/unit/book_test.rb
create test/fixtures/books.yml
create db/migrate
create db/migrate/20080616164236_create_books.rb
|
As you write code in the model classes, you'll write corresponding tests in these files. So let's create two test book records using YAML in test/fixtures/books.yml as follows:
perl_cb:
id: 1
title: 'Ruby Tutorial'
price: 102.00
description : 'This is a nice Ruby tutorial'
java_cb:
id: 2
title: 'Java Programming'
price: 62.00
description : 'Java Programming for the beginners'
|
Now let's relace exsiting code in book unit test file test/unit/book_test.rb with the following code:
require File.dirname(__FILE__) + '/../test_helper'
class BookTest < ActiveSupport::TestCase
fixtures :books
def test_book
perl_book = Book.new :title => books(:perl_cb).title,
:price => books(:perl_cb).price,
:description => books(:perl_cb).description,
:created_at => books(:perl_cb).created_at
assert perl_book.save
perl_book_copy = Book.find(perl_book.id)
assert_equal perl_book.title, perl_book_copy.title
perl_book.title = "Ruby Tutorial"
assert perl_book.save
assert perl_book.destroy
end
end
|
Finally, run the test method as follows:
testapp > ruby test/unit/book_test.rb
|
Here's the output of running the successful test case:
testapp > ruby test/unit/book_test_crud.rb
Loaded suite ./test/unit/book_test
Started
.
Finished in 0.0625 seconds.
1 tests, 4 assertions, 0 failures, 0 errors
|
What has happend here:
The BookTest method starts off by creating a new Book object using the title and other fields from the first record in the text fixture/books.yml. The resulting object is stored in the perl_book instance variable.
The first assertion tests that saving the Book object was successful.
Next, the book object is retrieved using the find method and stored in another instance variable named perl_book_copy. The success of this retrieval is tested in the next assertion, which compares the titles of both book objects. At this point, we've tested the ability to create and read a database record.
The solution tests updating by assigning a new title to the object stored in perl_book and then asserts that saving the change is successful.
Finally, the ability to destroy a Book object is tested.
This is how we can test our Rails Models.
Testing Controllers:
Controller testing is also known as functional testing. Functional testing
tests the following type of functionalities of the controllers:
- Is the response redirected as expected ?
- Is the expected template rendered?
- Is the routing as expected
- Does the response contain the expected tags?
Rails framework supports 5 types of requests and to write a functional test, you need to simulate any of the five HTTP request types that your controller will process. :
Request type "get" and "post" are the most commonly used in controller testing. 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
In this tutorial, we will see how to use get method to test our controller. You can test rest of the methods in similar way.
When you generate a controller with generate, Rails creates a functional test script for the controller as follows:
testapp > ruby script/generate controller Book
exists app/controllers/
exists app/helpers/
create app/views/book
exists test/functional/
create app/controllers/book_controller.rb
create test/functional/book_controller_test.rb
create app/helpers/book_helper.rb
|
As you write code in the controller classes, you'll write corresponding tests in these files. Before that let's define our controller functions list, show, and search inside app/controllers/book_controller.rb as follows:
class BookController < 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_title(params[:title])
if @book
redirect_to :action => 'show', :id => @book.id
else
flash[:error] = 'No such book available'
redirect_to :action => 'list'
end
end
end
|
NOTE: You would need two views templates for show and list method. You can define those views and test them, but right we will proceed without defining those views.
Now let's reuse our test fixture which is in test/fixtures/books.yml file as follows:
perl_cb:
id: 1
title: 'Ruby Tutorial'
price: 102.00
description : 'This is a nice Ruby tutorial'
java_cb:
id: 2
title: 'Java Programming'
price: 62.00
description : 'Java Programming for the beginners'
|
Add the following test_search_book and test_search_not_found methods to test/functional/book_controller_test.rb to test the functionality of the Book Controller's search action.
require File.dirname(__FILE__) + '/../test_helper'
require 'book_controller'
# Re-raise errors caught by the controller.
class BookController
def rescue_action(e)
raise e
end
end
class BookControllerTest < Test::Unit::TestCase
fixtures :books
def setup
@controller = BookController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_search_book
get :search, :title => 'Ruby Tutorial'
assert_not_nil assigns(:book)
assert_equal books(:perl_cb).title, assigns(:book).title
assert_valid assigns(:book)
assert_redirected_to :action => 'show'
end
def test_search_not_found
get :search, :title => 'HTML Tutorial'
assert_redirected_to :action => 'list'
assert_equal 'No such book available', flash[:error]
end
end
|
Now run your test cases as follows:
testapp > ruby test/functional/book_controller_test.rb
|
This gives following output:
Loaded suite test/functional/book_controller_test
Started
..
Finished in 0.422 seconds.
2 tests, 7 assertions, 0 failures, 0 errors
|
Let's see what has happened here:
The setup method is a default method to create controller, request and response objects. They would be used by Rails internally.
The first test method test_search_book generates a get request to the search action, passing in a title 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 inside first method tests that the request was redirected to the controller's show action.
The second test method, test_search_not_found, performs another get request but passes in an invalid title.
The first assertions test that a redirect to the list action was issued.
If the proceeding assertions passed, there should be a message in the flash hash which you can test with assert_equal.
For a complete detail on Assertions ,please refere to Rails Standard Documentation.
Using Rake for testing:
You can use rake utility to test your applications. Here are few important commands.
$rake test - Test all unit tests and functional tests (and integration tests, if they exist).
$rake test:functionals - Run all functional tests.
$rake test:units - Run all unit tests.
$rake test:integration - Run all integration tests.
$rake test:plugins - Run all test in ./vendor/plugins/**/test.
$rake test:recent - Run tests for models and controllers that have been modified in the last 10 minutes:
$rake test:uncommitted - For projects in Subversion, run tests for models and controllers changes since last commit:
|