Before proceeding, let's have a quick look at a 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.
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
Till now, we have used only Rails application's development database, but now you need to make sure that the testing database is also created and appropriate sections of your config/database.yml file are 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)
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
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. Thereafter, we need to set up your test database using rake command as follows −
C:\ruby\testapp > rake db:test:clone_structure
This will clone the testapp_development database into testapp_test database. It means whatever you have in the development database, now you will have the same data in the test database as well.
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 replace the existing 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
Let’s analyze what happened 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.
Controller testing is also known as functional testing. Functional testing tests the following type of functionalities of the controllers −
Rails framework supports five types of requests −
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 −
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 now, we will proceed without defining those views.
Now let's reuse our test fixture which is in the 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
It gives the 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 analyze 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..
To obtain more information on Assertions, please refer Rails Standard Documentation.
You can use rake utility to test your applications. Given below is a list of 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 the changes that took place in the models and controllers since the last commit −