Testing in Rails: Part 6 - Fixtures

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.

Fixtures

In the most general terms, a test fixture is an environment for running tests which is in a fixed state (i.e. it is ‘fixed’). While the term is primarily associated with software development, it applies to any testing environment.

Think back to our car battery example for a moment. If we unit test the battery inside the car, there is a chance that our testing environment will throw off the results. Ideally, we would remove the battery from the car and put it in a test environment that we know is stable, thereby removing as many variables and potential sources of error as possible. Then our unit test results should be predictable, accurate and repeatable. The environment we put the battery in—the meters, the wires attached to it, the amount of voltage we put in or out, etc.—is the test fixture. It will stay the same during each test we run on the battery.

Racetrack

You have encountered fixtures in other contexts before without knowing it. A race track is an example of fixture. The cars and drivers racing around it are different, but the pavement and curves are the same for everyone. When a race (which is essentially a benchmark test) pits the cars against each other, the fixtures are important in ensuring that the winner is meaningful. Everyone faces the same conditions, yet one car will be faster than the rest. If every car raced on a different course the race results would be meaningless. Sports and competitive events are filled with examples of fixtures—generally known as “having a level playing field.”

The idea is to fix the environment so that the environment will not skew the results.

Fixtures in Rails

When we talk about fixtures in Rails we are talking about data. More specifically, they are data that we can use to pre-populate records in our test database. These records will be available at the start of every test. It is similar to how we used the setup method to populate the database with data, only fixtures are faster to load, easier to write, and better for keeping our test data organized. Fixtures also solve the auto-increment problem we saw in the last section because we can provide an ID for each record in the fixtures data.

Fixtures are a part of the Rails framework, not Ruby. Rails fixtures are written in YAML (YAML Ain’t Markup Language) which is a simple way to store information in a flat text file. (It is also possible to write them in CSV or other formats but few developers do.) These YAML files are stored in the test/fixtures directory, and each fixtures file holds the data for a single ActiveRecord model. The file name has to be the same as the lowercase plural of the model name, which is also generally the name of the database table where the fixtures will be loaded. For example, the model Wine will have a fixtures file called “wines.yml” which will be loaded into the test database table called “wines”.

When we generated our models Rails created sample fixtures for us. Let’s take a look at what it gave us by opening test/fixtures/wines.yml.

1
2
3
4
5
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
  id: 1
two:
  id: 2

It is not terribly interesting but it does give us an idea of the structure. Each fixture begins with a reference name or label. We will use this name to refer to a particular fixture even though the name will not be loaded into the database. The sample gives the generic labels of “one” and “two”. You could use “wine_00001″ and “wine_00002″ instead. An even better suggestion would be to give each record a meaningful name like we did in our setup method: “merlot” and “pinot_noir”.

After each label is a list of key-value pairs with a tab indenting each one. Essentially, each entry is a hash represented in YAML and corresponds to a row in our database table. In the sample file, we only have the ID provided, but this shows the format for any other attributes we want to list. Below is my revised fixtures file.

1
2
3
4
5
6
7
8
9
10
merlot:
  id: 1
  name: Sunnyside Reserve
  year: 2003
  family: Merlot
pinot_noir:
  id: 2
  name: Valley Creek
  year: 1998
  family: Pinot Noir

Notice that in YAML you do not need to put quotes around string values. The one exception to this is strings which have a special meaning in YAML. For example, true and false in YAML become boolean true and false (0 and 1). If you want “true” to remain a string, then you need to put double-quotes around it. There are a few other cases to watch out for: Yes, No, Y, N, on, off. If you have doubts you can review the quick reference for the YAML specification.

Fixtures are Dynamic

One nice feature of fixtures is that they support dynamically generated data using ERb, just like in view templates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<% x = 0 -%>
<% y = 2003 -%>
<% families = ['Merlot', 'Pinot Noir'] -%>

merlot:
  id: <%= x += 1 %>
  name: Sunnyside Reserve
  year: <%= y %>
  family: <%= families[0] %>
pinot_noir:
  id: <%= x += 1 %>
  name: Valley Creek
  year: <%= y - 5 %>
  family: <%= families[1] %>

Those are a bit forced and not very useful examples, but they make the point.

You will find dynamic data to be the most useful with dates. Let’s say we need a unit test to have access to a wine that was created today and a wine that was created one week ago. A fixture is appropriate because we want it to always be fixed on today and exactly one week ago. But since the date the test is run will change, putting ‘2007-12-21 00:00:00′ and ‘2007-12-14 00:00:00′ into our fixtures will cause our tests to break later. Instead we need to use dynamic data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
merlot:
  id: 1
  name: Sunnyside Reserve
  year: 2003
  family: Merlot
  created_at: <%= 1.week.ago.to_s(:db) %>
  updated_at: <%= 1.week.ago.to_s(:db) %>
pinot_noir:
  id: 2
  name: Valley Creek
  year: 1998
  family: Pinot Noir
  created_at: <%= Time.now.to_s(:db) %>
  updated_at: <%= Time.now.to_s(:db) %>

You might be tempted to simply use Time.now but the formatting would be wrong (”Fri Dec 21 00:00:00 -0500 2007″). By calling the method .to_s(:db) the time gets converted to a string based on the current database type, ensuring that the date format is what the database prefers.

As of Rails 2.0 the IDs for each record are optional. If they are not provided IDs will be auto-generated, incrementing with each fixture. The auto-generation in fixtures is handled in such a way that you will not encounter the auto-increment problem we discussed earlier. It does no harm if you continue to specify the ID and, personally, I still prefer to list them.

Go ahead and create a fixtures file for Winery on your own. Put the Sunnyside Vineyards in Sonoma, CA, USA as the first entry.

Using Fixtures in Unit Tests

Now that we have a fixture file, we just need to tell our unit test to load it up. We do that with fixtures :wines. That line was already included in our unit test but we commented it out earlier. Uncomment it and your fixtures will load.

Consider that we need more than just the fixtures for wines. We will also need to load up the fixtures for wineries. We can load additional fixtures files by simply separating the symbols with commas. You will want to list every fixture file that will be used anywhere in this unit test.

Once they are loaded and available, we can now refer to our fixtures instead of creating new objects in the setup method. We do that by calling the name of the set of fixtures and then passing it a symbol for the label of the fixture we want to reference. The 2003 Sunnyside Reserve Merlot can be referenced as wines(:merlot). This is the same as calling Wine.find(1), but it is easier to read and Rails should find it faster.

Here is the revised setup method:

1
2
3
4
5
6
7
8
9
10
11
12
class WineTest < Test::Unit::TestCase
  fixtures :wines, :wineries

  def setup
    @sunnyside = wineries(:sunnyside)
    @merlot = wines(:merlot)
    @pinot_noir = wines(:pinot_noir)
    @sunnyside.wines = [@merlot, @pinot_noir]
  end
 
  ...
end

Try running your unit tests now. You should get something like:

1
2
3
4
5
6
7
>ruby test/unit/wine_test.rb
Loaded suite test/unit/wine_test
Started
..
Finished in 0.053467 seconds.

2 tests, 2 assertions, 0 failures, 0 errors

Our tests pass and the auto-increment problem has been solved. (Run the test again to see.)

You do not have to assign all your fixtures to instance variables in the setup method. Just assign the ones that you plan to use frequently. Each unit test can load up additional fixtures if it needs them.

Also, fixtures do not have to be assigned to instance variables. You could have something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
# just an example, no need to add it to your unit tests
class WineTest < Test::Unit::TestCase
  fixtures :wines, :wineries

  def setup
    @sunnyside = wineries(:sunnyside)
    @merlot = wines(:merlot)
  end
 
  def test_created_at_difference
    assert_equal(7.days, wines(:pinot_noir).created_at - @merlot.created_at)
  end
end

Notice that wines(:pinot_noir) is never assigned to an instance variable and that you can call the created_at method (or any instance method) directly on it.

Relating Records in Fixtures

You probably noticed that I am establishing the relationship between the wines and the wineries in the setup method. I started with it that way to illustrate that even with fixtures we can work with ActiveRecord objects and databases just as we do in our code. But in real life testing, providing each and every relationship is cumbersome. It would be much cleaner to relate these records in the fixtures and load them with their relationships, ready for use.

That is easily done. You just add an attribute for the foreign key to each fixture.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
merlot:
  id: 1
  name: Sunnyside Reserve
  year: 2003
  family: Merlot
  created_at: <%= 1.week.ago.to_s(:db) %>
  updated_at: <%= 1.week.ago.to_s(:db) %>
  winery_id: 1
pinot_noir:
  id: 2
  name: Valley Creek
  year: 1998
  family: Pinot Noir
  created_at: <%= Time.now.to_s(:db) %>
  updated_at: <%= Time.now.to_s(:db) %>
  winery_id: 1

Once the fixtures contain the foreign key, you can remove the line in setup, @sunnyside.wines = [@merlot, @pinot_noir], and run the unit tests again. They should still pass. The relationships are established from the start.

You will remember that I said starting with Rails 2.0 you no longer have to provide an ID. How can we declare a relationship to another record that does not have an ID yet? The solution is that Rails 2.0 also allows you to declare a relationship using the fixture’s label. Instead of declaring the foreign key, winery_id: 1, you can use the foreign fixture’s label, winery: sunnyside. So you could have:

1
2
3
4
5
6
# wineries.yml
sunnyside:
  name: Sunnyside Vineyards
  city: Sonoma
  state: CA
  country: USA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# wines.yml
merlot:
  name: Sunnyside Reserve
  year: 2003
  family: Merlot
  created_at: <%= 1.week.ago.to_s(:db) %>
  updated_at: <%= 1.week.ago.to_s(:db) %>
  winery: sunnyside
pinot_noir:
  name: Valley Creek
  year: 1998
  family: Pinot Noir
  created_at: <%= Time.now.to_s(:db) %>
  updated_at: <%= Time.now.to_s(:db) %>
  winery: sunnyside

Again, the original way still works, you just have other options if you prefer to work this way instead.

Faster Fixtures

Many developers write sections of code and add sample data to their development database before they start writing tests. It would be a shame to not make the most of that sample data and to write up the fixtures data from scratch.

Lucky for us, Geoffrey Grosenbach wrote a useful plugin called AR Fixtures which will dump your development database data into a YAML fixtures file. It even allows you to load your test fixtures back into the development database.

 

With fixtures to populate our test database, we can focus now on writing our unit tests.

UPDATE: The series continues in Part 7.

Bookmark and Share

2 Responses to “Testing in Rails: Part 6 - Fixtures”

  1. Groxx Says:

    Just a note:
    In Rails 2.1.0 at least, if you leave off the ID field, the ID is auto-generated not by incrementing, but by hashing the name, guaranteeing stability and uniqueness. As it could be problematic if people assume they can ‘guess’ the id, instead of correctly using the “advanced” features, you may wish to change the note here.

    reference:
    http://ar.rubyonrails.org/classes/Fixtures.html

  2. Albert Anthony Says:

    Question:
    I am using rails 2.3.4 and the ‘Label Reference’ seem not working?

    I have read http://ar.rubyonrails.org/classes/Fixtures.html

Leave a Reply