Testing in Rails: Part 4 - Unit Testing in Rails
This is part of an ongoing series of posts about how to get started writing tests for Ruby on Rails. The series begins with the introduction and overview of the ideas behind testing.
Unit Tests + Rails
After so much discussion of unit testing in Ruby you are probably anxious to start applying your new-found skills to a Rails application. The good news is that everything you have learned so far is directly applicable to testing in Rails.
When you first create a new Rails application (e.g. rails sample_app), a folder called ‘test’ is created in your application’s root directory. This is the folder where all of your test code will reside.

As you would expect, we will be putting all of our application’s unit tests inside the subfolder called ‘unit’. At first that folder will be empty. When a new model is generated, the generator script will also create a skeleton for unit testing that model.
Let’s stick with our Car model as an example.
1 2 3 4 5 6 7 8 9 10 11 12 | >rails sample_app ... (application files get created) >cd sample_app >script/generate model Car exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/car.rb create test/unit/car_test.rb create test/fixtures/cars.yml create db/migrate create db/migrate/001_create_cars.rb |
Notice that it created ‘test/unit/car_test.rb’ and ‘test/fixtures/cars.yml’. We will learn to use fixtures later. For now, all you need to know is that fixtures will provide a way to load sample data into a database that your tests can use.
Open up cart_test.rb.
1 2 3 4 5 6 7 8 9 10 | require File.dirname(__FILE__) + '/../test_helper' class CarTest < Test::Unit::TestCase fixtures :cars # Replace this with your real tests. def test_truth assert true end end |
Notice that this test does not load in Test::Unit as we did at the start of our Ruby tests (i.e. require ‘test/unit’). The Rails framework has already made sure Test::Unit is ready and waiting for us.
It does however load in ‘../test_helper’ which can be found in the root of the ‘test’ directory. This file will be a place to store methods that you want to be accessible to all of your applicaiton’s unit tests. It works a lot like helper methods in your Rails application. If you examine the file you will see that it adds methods to Test::Unit::TestCase which will be the superclass to all of your unit test classes.
The CarTest class should look pretty standard except for fixtures :cars. Comment that line out for now. We will come back to it later on.
One minor point: unlike much of Rails, your test class name and the file name that contains the class are not important. They do not have to be the same as your model name or have any relationship to each other. The file and class names the generator provides are useful because they clearly indicate that this test is related to your Car model, but Rails does not require you to adhere to those names.
Running Unit Tests in Rails
We can try running the tests in the same way that we ran our Ruby unit tests, by simply executing the test file with Ruby.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >ruby test/unit/car_test.rb Loaded suite test/unit/car_test Started EE Finished in 0.027768 seconds. 1) Error: test_truth(CarTest): Mysql::Error: Access denied for user 'root'@'localhost' (using password: NO) ... /activerecord-1.15.5/lib/active_record/fixtures.rb:538:in `setup` 2) Error: test_truth(CarTest): Mysql::Error: Access denied for user 'root'@'localhost' (using password: NO) ... /activerecord-1.15.5/lib/active_record/fixtures.rb:561:in `teardown` 1 tests, 0 assertions, 0 failures, 2 errors |
The unit test executed successfully but it generated two errors. (I snipped out the unimportant bits.) The errors are telling us that our tests could not connect to a database. It does not matter that we commented out the fixtures line. Rails still tries to connect to our test database, once during setup and once during teardown. (’teardown’ is a Test::Unit::TestCase method that works like ’setup’ but is called after the test is complete.)
Test Database
This highlights the first big difference between Rails testing and Ruby testing: we need a database. Since our tests will need to be able to test creating, updating and deleting records, we absolutely will not want to use our production database.
It is not a great idea to use our development database either. We need to be able to develop and test simultaneously. If we were developing with a set of sample records in our development database we might not want our tests to wipe out that data. Instead we should create a third database.
You may already be in the habit of creating and configuring three databases each time you start a project. If you are following along with my examples, go ahead and create a database called ’sample_app_test’ and edit the ‘test’ configuration in ‘config/database.yml’ to connect to it. You do not need to create or configure the development and production databases. The Rails testing framework will only use the database specified under ‘test’.
Try running the unit tests again and this time they should pass.
The Final Step
Take all the code we previously wrote for our Car class and paste it into ‘app/models/car.rb’. (The Car model could still inherit from ActiveRecord; the unit tests we are using will not use ActiveRecord to do anything. I went ahead and made Car a stand-alone class.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # app/models/car.rb class Car attr_accessor :model, :year, :color attr_reader :wheels attr_writer :doors def initialize(options={}) self.model = options[:model] || 'Volvo' self.year = (options[:year] || 2007).to_i self.color = options[:color] || 'unknown' @wheels = 4 end def self.colors ['blue', 'black', 'red', 'green'] end def full_name "#{self.year.to_s} #{self.model} (#{self.color})" end end |
Take all the code from our first CarTest class and paste it into ‘test/unit/car_test.rb’.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | # test/unit/car_test.rb require File.dirname(__FILE__) + '/../test_helper' class CarTest < Test::Unit::TestCase #fixtures :cars def setup @volvo = Car.new(:color => 'green') @honda = Car.new(:model => 'Honda', :year => 2004, :color => 'blue') @dodge = Car.new(:model => 'Dodge', :year => 2003, :color => 'yellow') end def test_attributes new_car = Car.new # write to the attributes new_car.model = 'Test' new_car.year = 25 new_car.color = 'test color' # read from the attributes assert_equal('Test', new_car.model) assert_equal(25, new_car.year) assert_equal('test color', new_car.color) assert_equal(4, new_car.wheels) #test the attr_reader and attr_writer # :wheels can be read but not written assert_nothing_raised(NoMethodError) { new_car.wheels } assert_raise(NoMethodError) { new_car.wheels = 6 } # :doors can be written but not read assert_nothing_raised() { new_car.doors = 2 } assert_raise(NoMethodError) { new_car.doors } end def test_initialize # try creating a new object without the optional options hash default_car = Car.new # test that default values were used assert_equal('Volvo', default_car.model) assert_equal(2007, default_car.year) assert_equal('unknown', default_car.color) # let's try again but with values in an option hash custom_car = Car.new(:model => 'Toyota', :year => 2005, :color => 'white') # test that options values were used instead of defaults assert_equal('Toyota', custom_car.model) assert_equal(2005, custom_car.year) assert_equal('white', custom_car.color) # leaving out :year; sending :color and :model out of order shuffled_car = Car.new(:color => 'red', :model => 'Kia') assert_equal('Kia', shuffled_car.model) assert_equal(2007, shuffled_car.year) assert_equal('red', shuffled_car.color) # testing :year as a string string_car = Car.new(:year => '2000') assert_equal(2000, string_car.year) end def test_colors assert(Car.colors.kind_of?(Array)) assert_equal(4, Car.colors.length) assert_equal(['blue', 'black', 'red', 'green'], Car.colors) assert(Car.colors.include?(@volvo.color)) assert(Car.colors.include?(@honda.color)) assert(!Car.colors.include?(@dodge.color)) assert(!Car.colors.include?(Car.new.color)) end def test_full_name assert_equal('2007 Volvo (green)', @volvo.full_name) @volvo.model = 'Nissan' @volvo.year = 2006 @volvo.color = 'black' assert_equal('2006 Nissan (black)', @volvo.full_name) @volvo.model = 'foo' @volvo.year = 1 @volvo.color = 'bar' assert_equal('1 foo (bar)', @volvo.full_name) end end |
Run your unit tests again. You should get “4 tests, 28 assertions, 0 failures, 0 errors”.
It is that easy! Of course, this is still just testing Ruby code by dropping it inside the Rails framework. But we are using the Rails framework and our application could legitimately have a model that does not inherit from ActiveRecord but still needs unit testing (a shopping cart, a widget or a menu bar come to mind as possibilities). Using the same Ruby code illustrates how similar unit testing is in Ruby and in Rails.
Next we will examine unit testing on a more typical Rails model, one that uses ActiveRecord and a database.
UPDATE: The series continues in Part 5.

December 28th, 2007 at 9:44 am
[...] Testing in Rails: Part 4 - Unit Testing in Rails - Next part of an ongoing beginners’ series from Null is Love. [...]