Testing in Rails: Part 7 - ActiveRecord Relationships
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 Testing ActiveRecord

In the unit tests we wrote previously, we tested everything in the class. The rule of thumb was that everything needs to be tested. That has not changed now that we are working with ActiveRecord—each method still needs a test.
But what testing should be done on the basic database CRUD (Create, Read, Update, Delete)? After all, that is what is most different about classes that inherit from ActiveRecord, right? It may surprise you to learn that we do not need to test basic CRUD.
Why not? ActiveRecord handles all of our database activity for us. It knows how to connect to the database, how to create, find, read, update, delete, use conditions and sort. That is the reason why we are using a framework like Rails to begin with: because we can inherit all that ActiveRecord goodness without having to rewrite it ourselves. We can trust that ActiveRecord will do its job correctly.
photo by Tomás Castelazo
That is not blind trust—ActiveRecord has its own unit tests that the core Rails team uses to insure that it works properly. Therefore we can assume that everything our class inherits from ActiveRecord is solid and tested. It was unit tested before we installed it. We just need to focus on our code in the subclass.
The corollary to “Test Everything” is: “If it is code you added, it is code you need to test.”
Unit Testing ActiveRecord Subclasses
We have established from previous unit testing that we need to test each of our custom methods. But what about the relationships and the validations that usually get defined at the top of our subclass? They are code we added and they definitely require testing. In fact, they are often the most important thing to test! Mistakes or bad assumptions in the relationships and validations will fundamentally affect your Rails application and the data you allow into your database.
Over time you will develop a testing workflow that suits you. One good approach is to write unit tests for each model’s relationships first. It is an easy way to get a basic unit test for each model in place quickly. Once you know you have the fundamental relationships right (and have worked out any problems) then you can move on to writing unit tests for each model’s validations. Once those tests pass you can feel confident that only good data can be put in the database. Then you can work through each model’s custom methods, knowing that the fundamentals are right. To understand why this sequence of work is helpful: imagine spending hours writing unit tests for all your custom methods, only to discover that you made a mistake in your relationships. The work you spent hours on would come crashing down like a tower of Jenga blocks and you would have to start over.
An alternate workflow might be to work through the relationships, validations and custom methods in the same order but to write the unit tests for one model at a time. (In my own work, I usually some mix of the two.)
The important thing to remember is the order: relationships, then validations, then custom methods.
Testing belongs_to Relationships
We are ready to test the model’s relationships. Ask yourself what the fundamental characteristics of a belong_to relationship are. If a child object belongs to a parent object then two things should be true:
- The child should have a foreign key equal to the id of the parent.
- Calling child.parent should return the parent object
You can probably think of other truisms, but these two are the most important in my opinion. It ensures that the relationship data is correct in the database and that our Rails model uses that data correctly to make the relationship. We can use these to write our test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class WineTest < Test::Unit::TestCase fixtures :wines, :wineries def setup @sunnyside = wineries(:sunnyside) @merlot = wines(:merlot) @pinot_noir = wines(:pinot_noir) end def test_relationships # belongs_to :winery assert_equal(1, @merlot.winery_id) assert_equal(1, @merlot.winery.id) assert_equal("Sunnyside Vineyards", @merlot.winery.name) end end |
First, I check to make sure the foreign key is present and what I expect. Next, I use the relationship to retrieve the parent object and make sure its ID is what I expect. Finally, I use the relationship to check another property of the parent object. This last step is not essential. I like it because it reassures me that I ended up with the parent object I was expecting. If the records had been saved in a different order, the parent with ID 1 might not be what you assume it is. You can use any attribute of the parent to double-check—I just try to pick one that is unique.
You will notice the comment (”# belongs_to :winery”) before the relationship test. For me, this is standard practice. I go into my model, copy all the relationships, paste them into a unit test (usually called ‘test_relationships’), comment them out, and then write tests for each relationship below its comment. You may find that helpful too.
Let’s run our belongs_to relationship test.
1 2 3 4 5 6 7 | >ruby test/unit/wine_test.rb Loaded suite test/unit/wine_test Started . Finished in 0.591909 seconds. 1 tests, 3 assertions, 0 failures, 0 errors |
Our test passes and we can now be confident that our belongs_to relationship is working.
It is also worth noting that this technique for testing belongs_to would be the same whether this is the “other half” of a has_many or a has_one relationship. belongs_to works the same in both cases, so it is good that our testing technique does too.
Testing has_many Relationships
Writing a test for has_many relationships is just as easy. If a parent object has many child objects then two things should be true:
- Each child should have a foreign key equal to the id of the parent.
- Calling parent.children should return an array of the children of the parent.
We have not written any code for WineryTest yet, but you should know how to get started. We can make use of the same fixtures as WineTest, and we can copy the WineTest setup method to instantiate objects for testing.
After that, we can test the has_many relationship by calling the .wines method on the winery instance (the parent). That will return the children. We can do several things with those children, but the two most important ones are to make sure that they are all there and that they have the right foreign key. I do that with the count method and by collecting their IDs together. Just as I did with belongs_to, I also like to test one of the values “across” the relationship to make ensure it returns the expected value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | require File.dirname(__FILE__) + '/../test_helper' class WineryTest < Test::Unit::TestCase fixtures :wines, :wineries def setup @sunnyside = wineries(:sunnyside) @merlot = wines(:merlot) @pinot_noir = wines(:pinot_noir) end def test_relationships # has_many :wines assert_equal(2, @sunnyside.wines.count) assert_equal([1,1], @sunnyside.wines.collect {|w| w.winery_id }) assert_equal("Sunnyside Reserve", @sunnyside.wines.sort_by {|w| w.name }.first.name) end end |
Take a closer look at the second assertion in test_relationships. It works well for half a dozen children or less, but it becomes cumbersome when you have 32 related items.
You may remember from Ruby that: [x,x,x,x] == [x] * 4. So a better way to write the second assertion in test_relationships would be:
1 | assert_equal([1]*2, @sunnyside.wines.collect {|w| w.winery_id }) |
The first assertion (counting the children) is not strictly necessary—the second assertion also tests that there are two children, but I find it clearer and easier to debug if they are separate. One tests the quantity, the other tests their IDs.
In the third assertion, I could have used several different techniques to retrieve an attribute of a child object. I opted for sorting the children by name and then testing the name of the first child. You could use another technique, such as detect or include?, instead.
Testing the Order of a has_many Relationship
Sorting raises an interesting point. Winery’s has_many relationship is actually defined as has_many :wines, :order => ‘name ASC’. So, strictly speaking, I do not need to perform the sort. But I should test that the sort defined with the relationship is working.
Here’s a simple way to test it:
1 2 3 4 5 6 7 8 9 10 11 12 | class WineryTest < Test::Unit::TestCase ... def test_relationships # has_many :wines assert_equal(2, @sunnyside.wines.count) assert_equal([1]*2, @sunnyside.wines.collect {|w| w.winery_id }) # -- should sort by :order => 'name ASC' assert_equal("Sunnyside Reserve", @sunnyside.wines.first.name) assert_equal("Valley Creek", @sunnyside.wines.last.name) end end |
Admittedly, with only two children our testing is feeble, but you get the idea. With 13 wines we could test the first, the last, and the seventh wine and feel more confident that the sorting was performed correctly.
A better, more thorough test would be to loop through each wine and make sure each name is in fact greater than the one that preceded it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class WineryTest < Test::Unit::TestCase ... def test_relationships # has_many :wines assert_equal(2, @sunnyside.wines.count) assert_equal([1]*2, @sunnyside.wines.collect {|w| w.winery_id }) assert_equal("Sunnyside Reserve", @sunnyside.wines.first.name) # -- should sort by :order => 'name ASC' for wine in @sunnyside.wines previous_name ||= '' assert(previous_name <= wine.name) previous_name = wine.name end end end |
Do not be afraid to put loops into your tests! They are extremely useful. If you find yourself testing the sort order frequently, it might make sense to turn it into an independent function and put it into test_helper.rb. We will look at some examples on how to do that when we discuss testing validations.
Testing has_one Relationships
Unit testing has_one relationships is very similar to has_many and belongs_to. If a parent object has one child object then two things should be true:
- The child should have a foreign key equal to the id of the parent.
- Calling parent.child should return the child of the parent.
Rather than invent a new class, let’s imagine for a moment that each winery only had one wine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class WineryTest < Test::Unit::TestCase fixtures :wines, :wineries def setup @sunnyside = wineries(:sunnyside) @merlot = wines(:merlot) end def test_relationships # has_one :wine assert_equal(1, @sunnyside.id) assert_equal(1, @sunnyside.wine.winery_id) assert_equal("Sunnyside Reserve", @sunnyside.wine.name) end end |
(If you want to actually try this you will need to edit the relationship in Wine model and also your wine fixtures so that merlot is the only child of sunnyside.)
Testing has_and_belongs_to_many Relationships
has_and_belongs_to_many (HABTM) relationships are rare birds that become rarer every day. Most of the time it will make more sense to use a “rich join”. But in those cases where you still need the simplicity of HABTM, you should know how to test it.
For example, wines can be made from several types of grapes, properly known as “varietals”. A wine could include one varietal, or two, or three… Merlot and Pinot Noir wines are made from single varietals, also called Merlot and Pinot Noir. But a Bordeaux wine is often a mix of Merlot, Cabernet Sauvignon and Cabernet Franc. This is a good case for using a HABTM relationship.
We have done all these steps before but it is worthwhile to review the entire process, from model creation to unit test.
Model: Generate a model for Varietal. The generator will create files for our unit tests and fixtures at the same time.
1 | >ruby script/generate model Varietal |
Migration: Next we will create a migration for the varietals table and also the join table for the HABTM relationship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class CreateVarietals < ActiveRecord::Migration def self.up create_table :varietals do |t| t.column :name, :string end create_table :varietals_wines, :id => false do |t| t.column :varietal_id, :integer t.column :wine_id, :integer end end def self.down drop_table :varietals drop_table :varietals_wines end end |
Migrate: Use rake db:migrate to create the table in your development database. Then use rake db:test:prepare to move the development schema over to your test database. (Or migrate it there.)
Relationships: Next, create the HABTM relationships in the models.
1 2 3 4 5 6 7 8 9 | class Varietal < ActiveRecord::Base has_and_belongs_to_many :wines end class Wine < ActiveRecord::Base belongs_to :winery has_and_belongs_to_many :varietals ... end |
Fixtures: Create some fixtures for the varietals in test/fixtures/varietals.yml.
1 2 3 4 5 6 7 8 9 10 11 12 | merlot: id: 1 name: Merlot pinot_noir: id: 2 name: Pinot Noir cab_sauv: id: 3 name: Cabernet Sauvignon cab_franc: id: 4 name: Cabernet Franc |
I also added a new wine, a varietal blend from Bordeaux, to wines.yml.
1 2 3 4 5 6 7 8 | bordeaux: id: 3 name: Fig Leaf year: 2005 family: Bordeaux created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> winery_id: 2 |
What about adding fixtures for the join table varietals_wines? The table does not have a model so we do not have a fixture file waiting for us. We will need to create our own file and call it “varietals_wines.yml” (the same name as the target table).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | one: wine_id: 1 varietal_id: 1 two: wine_id: 2 varietal_id: 2 three: wine_id: 3 varietal_id: 1 four: wine_id: 3 varietal_id: 3 five: wine_id: 3 varietal_id: 4 |
Then we can reference it at the start of our tests just like the other fixtures.
1 2 3 4 5 | class WineTest < Test::Unit::TestCase fixtures :wines, :wineries, :varietals, :varietals_wines ... end |
Testing: Now we are finally read to test the HABTM relationship.
With HABTM relationships we can access the related objects but we cannot directly query the join table between them. That means we will not be able to test the values of the foreign keys, as we have done in the past, because they are stored in the join table. If an object has and belongs to many objects then three things should be true: (even though it is not a parent-child relationship, I am using ‘parent’ to indicate the starting object)
- Calling parent.associations should return an array of the associated objects.
- Any object in the parent’s association will include the parent in its association back to the parent’s class.
- Any object not in the parent’s association will not contain the parent in its association to the parent’s class.
The second statement sounds more confusing than it actually is. It basically says that HABTM relationships must go both ways—if a wine includes a varietal then that varietal will have a relationship (varietal.wines) that includes the wine. Testing that will ensure that the join table is properly joining the two classes. The third statement says that the negative is also true—if a wine does not include a varietal then varietal.wines will not include the wine.
Here is what the test would look like.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class WineTest < Test::Unit::TestCase fixtures :wines, :wineries, :varietals, :varietals_wines def setup ... @bordeaux = wines(:bordeaux) end def test_relationships ... # has_and_belongs_to_many :varietals assert_equal(3, @bordeaux.varietals.count) @bordeaux.varietals.each do |var| assert(var.wines.include?(@bordeaux)) end (Varietal.find(:all) - @bordeaux.varietals).each do |var| assert(!var.wines.include?(@bordeaux)) end assert_equal("Cabernet Franc", @bordeaux.varietals.sort_by {|v| v.name}.first.name) end end |
First, I tested whether the relationship returned the associated records and counted them, just like we did with has_many. Next, I looped through each of the Bordeaux’s varietals and made sure that each one also had an association back to the Bordeaux. Then I did the negative case. I found all the varietals possible, removed the ones that the Bordeaux has, and looped through each one to make sure that there were no associations to the Bordeaux. Finally, I tested that I was able to view attributes across the association.
You can practice this HABTM technique on your own by writing unit tests for the model Varietal we just created.
That covers all four ActiveRecord relationship types. The only one that is even slightly difficult is HABTM. I think after you write a few unit tests for has_many and belongs_to relationships, you will be able to code them very quickly.
UPDATE: The series continues in Part 8.

January 9th, 2008 at 8:00 am
[...] Testing in Rails: Part 7 - ActiveRecord Relationships - More from Null is Love. [...]
March 18th, 2008 at 9:12 am
Testing relationships in Rails…
Ler este artigo em português
I’ve been looking for an elegant way to test relationships using Rais, and think it’s interesting how so few people really care about it. Most simply say that it’s up to the framework to test it’s o…
August 3rd, 2008 at 10:56 pm
In the first relationship test, you wrote:
def test_relationships
# belongs_to :winery
assert_equal(1, @merlot.winery_id)
assert_equal(1, @merlot.winery.id)
[...]
Could we just write this?
def test_relationships
# belongs_to :winery
assert_equal(@merlot.winery.id, @merlot.winery_id)
[...]
Thanks
Denis
August 4th, 2008 at 8:48 am
@Denis: Yes, that would work too. As long as you understand the difference.
In my example, the idea is that I’m testing two things to make sure they are the EXACT value I expect. In your example, the idea is that you don’t care what value they have, just that they are equal. If @merlot.winery_id and @merlot.winery.id both have a value of 4 then my tests will fail and yours will pass.
The way you decide to write your tests will determine if that distinction makes a difference or not.
November 27th, 2008 at 10:50 am
This is by far the most helpful post about Rails 2+ model relationship testing I have found. And just to keep the ball rolling, I wanted to point out that with Foxy Fixtures in Rails 2+, we don’t even need to setup the join table fixtures (in this case, “varietals_wines”). Saves some time and effort and keeps tests running accurately in the future. For instance, with the awesome HABTM relationship test Kevin has shared with us, we can simply omit the varietals_wines.yml fixture and have our wine unit test look this this…
class WineTest < ActiveSupport::TestCase
# omitted unneeded :varietals_wines fixture
fixtures :wines, :wineries, :varietals
def setup
…
@bordeaux = wines(:bordeaux)
end
def test_relationships
…
# has_and_belongs_to_many :varietals
assert_equal(3, @bordeaux.varietals.count)
@bordeaux.varietals.each do |var|
assert(var.wines.include?(@bordeaux))
end
(Varietal.find(:all) - @bordeaux.varietals).each do |var|
assert(!var.wines.include?(@bordeaux))
end
assert_equal(”Cabernet Franc”, @bordeaux.varietals.sort_by {|v| v.name}.first.name)
end
end
January 8th, 2009 at 12:07 pm
This is an excellent post! I’m glad I finally found it. I have only one comment - I would suggest NOT having one big “test_relationships” method, but rather breaking up the tests into, for example, “test_belongs_to_winery” and “test_has_and_belongs_to_many_varietals”.
This is partly personal style, but there is a good reason behind it too. For one thing, if you have one big method, and the belongs_to test breaks, it precludes the later tests from being run. So one failure can mask another. For another, using smaller methods lets you name them more descriptively.
Great post though - thanks!
April 9th, 2010 at 1:48 pm
Your post has been extremely useful at helping me understand the nature of AR associations and how to test them. Thank you!. I recently discovered that the shoulda gem contains macros for testing these associations as well. I highly recommend any readers first use this post to understand the nature of testing the associations and then afterwards check out implementing shoulda so you can do things like:
should_have_many :tags, :through => :taggings
http://wiki.github.com/thoughtbot/shoulda/usage
May 6th, 2010 at 3:02 pm
Very helpful, but could you publish (and/or e-mail me) the contents of your fixtures? I’m still sorting through the confusing of trying out relationship associations from the console vs. in the test scripts.
May 7th, 2010 at 11:18 am
Steve: All the fixtures are shown, either on this post or in the previous one.
May 14th, 2010 at 4:01 pm
This was by far the most useful column I’ve seen on testing. Thanks very much! The one thing that lingers as confusing for me is that my actual ID generated by rails doesn’t seem to match up with a manually hashed value of the symbol.
For example, in order to test assert_equal(1, @merlot.winery_id), this fails because my rails version (2.3.5) hashes the name of the YAML row label “sunnyside:” in the wineries.yml fixture and loads that into the database as 350710738. So in order to test this I have to do either the following:
assert_equal(350710738, @merlot.winery_id) OR
assert_equal(@merlot.winery.id, @merlot.winery_id)
If, in IRB, I manually check the value of sunnyside, it is
irb> “sunnyside”.hash.abs
=> 528506201
My fixture is definitely using the label
sunnyside:
…
Do you know if for some reason this version of rails is appending characters to symbols in order to determine the ID values? Seems there is an extra string added or something?
May 17th, 2010 at 11:10 am
Steve: Did you give sunnyside an explicit ID? Maybe the YAML formatting is off? I believe the current behavior is to use the ID if provided, otherwise to create a hash of the label to use.
Here are more details on how it works:
http://ryandaigle.com/articles/2007/10/26/what-s-new-in-edge-rails-fixtures-just-got-a-whole-lot-easier
Since I wrote this, it has become more common to create fixtures *without* providing an explicit ID and to use the hashed value of the name as the ID instead. But both should still work.