Testing in Rails: Part 5 - Unit Testing ActiveRecord Models
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.
Setting Up for Unit Testing ActiveRecord Models

In the previous section, we saw that running unit tests inside the Rails framework is not that different from running tests outside it. We have learned how to test the Car class in both Ruby and Rails. But Car does not inherit from ActiveRecord::Base (or, if yours did, our tests did not probe any ActiveRecord traits). Let’s see how our unit tests would be different if our class was using ActiveRecord to store instances in a database.
For this example, we will keep using the same sample application, but we will move away from cars and create two new classes: Winery and Wine. Each winery will produce several different wines. We start by using script/generate to create our two models.
script/generate model Wine
The generator will create the model, a migration, a skeleton for your unit test and even a fixture file we will use later, all in one easy step. You do not need to worry about creating controllers, scaffolding or views. They are irrelevant for unit testing.
We will keep the tables simple. Here are my two migration files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class CreateWineries < ActiveRecord::Migration def self.up create_table :wineries do |t| t.column :name, :string t.column :city, :string t.column :state, :string t.column :country, :string t.column :created_at, :datetime t.column :updated_at, :datetime end end def self.down drop_table :wineries end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class CreateWines < ActiveRecord::Migration def self.up create_table :wines do |t| t.column :name, :string t.column :year, :integer t.column :family, :string t.column :winery_id, :integer, :null => false, :default => 0 t.column :created_at, :datetime t.column :updated_at, :datetime end add_index :wines, :winery_id end def self.down drop_table :wines end end |
If you never created a development database for sample_app (I had not), then you should do that now and provide the connection information in config/database.yml. Then run your migrations.
1 | rake db:migrate |
Notice that, when you migrate, it adds the tables to the development database but not to your test database. If we are going to test these ActiveRecord models in our test environment and want them to behave exactly like they do in our development and production environment, then it makes sense that we have to get the same schema into our test database. There are many ways to accomplish it and we will examine a few.
Rake Database Tasks
The first way to get the database schema into our test database is simply to perform another migration.
1 | rake db:migrate RAILS_ENV=test |
You have probably done something similar when migrating your development and production databases. Of course this technique depends on you having good migration files. Hopefully you do, but I also realize some people have not started using migrations yet.
Rake also provides some tasks that are specifically designed for working with your test database. You may find these Rake tasks easier in some cases, and they are essential if you are not using migrations.
| Rake Command | Description | Same as… |
|---|---|---|
| rake db:test:purge | “Empty the test database” | |
| rake db:test:clone | “Recreate the test database from the current environment’s database schema” | rake db:schema:dump rake db:test:purge rake db:schema:load |
| rake db:test:clone_structure | “Recreate the test databases from the development structure” | rake db:structure:dump rake db:test:purge execute the SQL commands |
| rake db:test:prepare | “Prepare the test database and load the schema” | rake db:test:clone or rake db:test:clone_structure |
The first task simply purges the test database. That is, it drops every table in the database but leaves the database itself.
The next three tasks will create the test databases that we need. All three will give you the same results, but I want to explain the differences. The db:test:clone task exports the schema for the current environment (development) into db/schema.rb, purges the test database, then loads the schema file into the test database. The db:test:clone_structure task does something similar but it exports the database structure as SQL into db/development_structure.sql, not as a Ruby Migration, then purges the test database and loads in the SQL from the file that it exported. If you have configured ActiveRecord::Base.schema_format to be :sql, then this is probably a better choice than db:test:clone. Finally, db:test:prepare is a wrapper for the previous two tasks. It uses the value of ActiveRecord::Base.schema_format (which is :ruby by default) to choose which of the first two tasks to call. If the value is :ruby, then it will run db:test:clone. If value is :sql, then it will run db:test:clone_structure.
Try out all three. There is no harm in it. Use the db:test:purge task if you want to drop all the tables before trying another task. I think rake db:test:prepare is the best choice simply because then you never have to remember what your schema_format is. Note that all of these techniques do not use your migration files—in fact, you do not even have to have migrations—they get schema information directly from the database for the current environment.
Building Out the Models
Now that we have a database that our ActiveRecord models can use during testing, let’s work on our models. They will be simple, but still look like models we might actually use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Winery < ActiveRecord::Base has_many :wines, :order => 'name ASC, year ASC' validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 255 validates_length_of :city, :maximum => 255 validates_length_of :state, :maximum => 255 validates_length_of :country, :maximum => 255 def location return [city, state, country].compact.join(", ") end end |
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 | class Wine < ActiveRecord::Base belongs_to :winery validates_presence_of :name, :year, :family validates_length_of :name, :maximum => 100 validates_format_of :year, :with => /\d{4}/, :message => 'must be a four-digit number' validates_length_of :family, :maximum => 255 FAMILIES = [ "Cabernet Sauvignon", "Merlot", "Pinot Noir", "Chardonnay", "Sauvignon Blanc" ].freeze def self.current_families all_wines = self.find(:all, :order => 'family ASC') return all_wines.collect {|w| w.family}.uniq! end def self.find_newest self.find(:first, :conditions => 'year IS NOT NULL', :order => 'year DESC') end def self.find_oldest self.find(:first, :conditions => 'year IS NOT NULL', :order => 'year ASC') end def listing_name(brief=true) if brief return "#{name}, #{family} (#{year})" else output = "#{name}, a #{family} from #{year}, by #{self.winery.name}" output << " in #{self.winery.location}." if self.winery.location != "" return output end end def age Time.now.year - self.year end def is_antique? self.age >= 75 ? true : false end end |
WineTest Setup
Before we can start testing, we will need some sample data to use. This is similar to what we did with the Car class earlier. The only difference is that we will save our instances to the database. (Strictly speaking, you don’t have to save them but you generally will.) Let’s create the setup method for WineTest (test/unit/wine_test.rb).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | require File.dirname(__FILE__) + '/../test_helper' class WineTest < Test::Unit::TestCase #fixtures :wines def setup @sunnyside = Winery.create(:name => "Sunnyside Vineyards", :city => "Sonoma", :state => "CA", :country => "USA") @merlot = Wine.create(:name => "Sunnyside Reserve", :year => 2003, :family => "Merlot") @pinot_noir = Wine.create(:name => "Valley Creek", :year => 1998, :family => "Pinot Noir") @sunnyside.wines = [@merlot, @pinot_noir] end end |
Note that I commented out the fixtures line again. We will get to fixtures soon though.
In the setup method I created a winery and two wines using sample data. I called create instead of new so that the instances will be saved to the database right away. Now we have some database data to use in our tests. Remember, you can use any Ruby you want to get the objects and the database tables into exactly the state you need. Use Ruby to set the stage.
The Pitfall in Creating Test Records
Before we start writing our tests, I need to show you a potential pitfall that comes when you create records in your tests. Especially if you create them in the setup method. Try adding this simple test to WineTest.
1 2 3 4 5 6 7 8 | class WineTest < Test::Unit::TestCase ... def test_id assert_equal(1, @merlot.winery_id) end end |
Now run your test.
1 2 3 4 5 6 7 | >ruby test/unit/wine_test.rb Loaded suite test/unit/wine_test Started . Finished in 0.053157 seconds. 1 tests, 1 assertions, 0 failures, 0 errors |
It worked. Big deal. But now try running the test a second time.
1 2 3 4 5 6 7 8 9 10 11 12 | >ruby test/unit/wine_test.rb Loaded suite test/unit/wine_test Started F Finished in 0.061369 seconds. 1) Failure: test_id(WineTest) [test/unit/wine_test.rb:15]: <1> expected but was <2>. 1 tests, 1 assertions, 1 failures, 0 errors |
What happened? We did not change any code. Why did the first succeed and the second fail? It reports that the problem was that the winery_id was 2 instead of 1. We might guess that the database records created in our first test were left behind (i.e. a winery with ID 1) which would cause a second winery to be given an ID of 2. But if we look in the test database, we will find that the wines and wineries tables are empty.
How the Test Database is Managed
Two things are happening to give us this strange behavior and they are important to understand.
1) The tests are performed as transactions, meaning that the database is being rolled back to its original state after the test is complete. If we create a record in setup that record will be removed at the end. That explains why the tables in the test database are empty.
2) The id field of both tables is set to ‘auto_increment’ and the database keeps track of the next number to use as an index. When the records created in the first test are deleted, the AUTO_INCREMENT value is not changed. It continues to count upward after each test’s setup method is called.
A simple rake db:test:prepare before running the tests would drop all of the tables in the test database and recreate them, thereby also setting AUTO_INCREMENT back to 0. That would make our relationship test run successfully (try it and see), but it would not keep AUTO_INCREMENT from increasing with each test in WineTest.
To see what I mean, try adding another simple test method to WineTest. It is important that the test is called “test_first” so that it executes before “test_id”.
1 2 3 4 5 6 7 8 | class WineTest < Test::Unit::TestCase ... def test_first assert true end end |
Now run your tests again making sure to prepare the test database first.
1 2 3 4 5 6 7 8 9 10 11 12 13 | >rake db:test:prepare >ruby test/unit/wine_test.rb Loaded suite test/unit/wine_test Started .F Finished in 0.096135 seconds. 1) Failure: test_id(WineTest) [test/unit/wine_test.rb:19]: <1> expected but was <2>. 2 tests, 2 assertions, 1 failures, 0 errors |
Setup was run twice, once before each test. Both times it created a new winery and the value of AUTO_INCREMENT increased even though it deleted the records at the end of each test. rake db:test:prepare got our database into shape before we started but it could not help once the tests were running.
Remember: when you add records to the database, the value of AUTO_INCREMENT will not be reset and may become difficult to predict.
I realize that seems kinda sucky. You could write your tests to take into account the non-resetting behavior of AUTO_INCREMENT. The downside would be that adding a test might throw the IDs in all of your other tests off.
But I think there is another solution that will make it suck less. But, even with this solution, you will still need to keep this fact about auto-incrementing in mind anytime you save new records to the database.
What can help to ease our setup pain? Fixtures.
(I told you we would get to fixtures soon enough…)
UPDATE: The series continues in Part 6.

December 19th, 2007 at 3:42 am
Pls go on this series, I need it, and I think many reader here need it two !
December 19th, 2007 at 7:15 am
More coming real soon… I promise!
End of the year client work and holiday shopping have taken up the spare time I usually use to write these.
December 23rd, 2007 at 10:18 am
[...] Testing in Rails: Part 5 - Unit Testing ActiveRecord Models - Part of the ongoing series from Null is Love. [...]
July 10th, 2008 at 10:12 pm
Hi Kevin,
Wow, that auto-increment and unit testing interaction is tricky. I haven’t saved any data via unit-test yet, so your very lucid explanation was a great eye-opener. Thanks for taking the trouble to announce the problem and illuminate the solution all at once.
Best wishes,
Richard
February 12th, 2009 at 2:30 pm
Thanks! I’ve been meaning to learn about testing for ages. This is really helpful, working myself through it. the id issue with autoincrementing is particularly useful, looking forward to the callbacks testing!
Daniela
May 26th, 2009 at 4:50 am
Hi
I am using rails 2.3.2 But the autoincrementing issue is not there. Am I right?
sk