Creating Fixtures for Many-to-Many Associations

Problem

Creating test fixtures for simple tables that don't have any relations to other tables is easy. But you have some Active Record objects with many-to-many associations. How do you populate your test database with data to test these more complex relationships?

Solution

Your database contains assets and tags tables as well as a join table named assets_tags. The following migration sets up these tables:

db/migrate/001_build_db.rb:

class BuildDb < ActiveRecord::Migration
 def self.up
 create_table :assets do |t|
 t.column :name, :string
 t.column :description, :text
 end
 create_table :tags do |t|
 t.column :name, :string
 end
 create_table :assets_tags do |t|
 t.column :asset_id, :integer
 t.column :tag_id, :integer
 end
 end
 def self.down
 drop_table :assets_tags
 drop_table :assets
 drop_table :tags
 end end

The Asset and Tag classes have Active Record many-to-many associations with each other:

app/models/asset.rb:

class Asset < ActiveRecord::Base
 has_and_belongs_to_many :tags end

app/models/tag.rb:

class Tag < ActiveRecord::Base
 has_and_belongs_to_many :assets 
end

To create YAML test fixtures to populate these tables, start by adding two fixtures to tags.yml:

test/fixtures/tags.yml:

travel_tag:
 id: 1
 name: Travel office_tag:
 id: 2
 name: Office

Likewise, create three asset fixtures:

test/fixtures/assets.yml:

laptop_asset:
 id: 1 
 name: Laptop Computer desktop_asset:
 id: 2 
 name: Desktop Computer projector_asset:
 id: 3
 name: Projector

Finally, to associate the tags and assets fixtures, we need to populate the join table. Create fixtures for each asset in assets_tags.yml with the id from each table:

test/fixtures/assets_tags.yml:

laptop_for_travel:
 asset_id: 1
 tag_id: 1
desktop_for_office:
 asset_id: 2
 tag_id: 2
projector_for_office:
 asset_id: 3
 tag_id: 2

Discussion

You include one or more fixtures by passing them as a comma-separated list to the fixtures method. By including all three fixture files in your test case class, you'll have access to assets and can access their tags:

test/unit/asset_test.rb:

require File.dirname(__FILE__) + '/../test_helper'
class AssetTest < Test::Unit::TestCase
 fixtures :assets, :tags, :assets_tags
 def test_assets
 laptop_tag = assets('laptop_asset').tags[0]
 assert_kind_of Tag, laptop_tag
 assert_equal tags('travel_tag'), laptop_tag
 end end

See Also