7. The Lo-Down on Fixtures

The structure is one thing, but what about when I want to automatically create sample data?

Enter fixtures. Fixtures is a fancy word for ‘sample data’. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: YAML or CSV.

You’ll find fixtures under your test/fixtures directory. When you run script/generate model to create a new model, fixture stubs will be automatically created and placed in this directory.

YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the .yml file extension (as in users.yml).

On any given Sunday, a YAML fixture file may look like this:

# low & behold!  I am a YAML comment!
david:
 id: 1 
 name: David Heinemeier Hansson 
 birthday: 1979-10-15 
 profession: Systems development

steve:
 id: 2
 name: Steve Ross Kellock
 birthday: 1974-09-27
 profession: guy with keyboard

Each fixture is given a “name” followed by an indented list of colon-separated key/value pairs. Records are separated by a blank space. You can place comments by using the # character in the first column.

Fixtures can also be described using the all-too-familiar comma-separated value file format. These files, just like YAML fixtures are placed in the test/fixtures directory, but these end with the .csv file extension (as in celebrity_holiday_figures.csv).

A CSV fixture looks like this:

id, username, password, stretchable, comments
1, sclaus, ihatekids, false, I like to say ""Ho! Ho! Ho!"" 
2, ebunny, ihateeggs, true, Hoppity hop y'all
3, tfairy, ilovecavities, true, "Pull your teeth, I will" 

The first line is the header. It is a comma-separated list of fields. The rest of the file is the payload: 1 record per line. A few notes about this format:

  • each cell is stripped of outward facing spaces
  • if you use a comma as data, the cell must be encased in quotes
  • if you use a quote as data, you must escape it with a 2nd quote
  • don’t use blank lines
  • nulls can be achived by just placing a comma, for example, (1,sclaus,,false,) minus the parenthesis of course.

Unlike the YAML format where you give each fixture a name, CSV fixture names are automatically generated. They follow a pattern of “model-name”-”counter”. In the above example, you would have:

celebrity-holiday-figures-1
celebrity-holiday-figures-2
celebrity-holiday-figures-3

The CSV format is great to use if you have existing data in a spreadsheet or database and you are able to save it (or export it) as a CSV.

ERb allows you embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb. This allows you to use Ruby to help you generate some sample data.

I’ll demonstrate with a YAML file:

<% earth_size = 20 -%>
mercury:
  id: 1
  size: <%= earth_size / 50 %>

venus:
  id: 2
  size: <%= earth_size / 2 %>

mars:
  id: 3
  size: <%= earth_size - 69 %>

Anything encased within the

<% -%>

tag is considered Ruby code. To actually print something out, you must use the

<%= %>

tag.

Rails makes no assumptions when it comes to fixtures. You must explicitly load them yourself by using the fixtures method within your TestCase. For example, a users model unit test might look like this:

# allow this test to hook into the Rails framework
require File.dirname(__FILE__) + '/../test_helper'

# we're testing a User, so we need to include it
require 'user'

class UserTest < Test::Unit::TestCase

  fixtures :users

  # count the fixtures
  def test_count_my_fixtures
    assert_equal 5, User.count
  end

end

Using the fixtures method and placing the symbol name of the model, Rails will automatically load up the fixtures for you at the start of each test method.

fixtures :users

What exactly does this line of code do? It does 3 things:

  • it nukes any existing data living in the users table
  • it loads the fixture data (if any) into the users table
  • it dumps the data into a variable in case you want to access it directly

So, in the above example, if we had another test method, we wouldn’t have 10 users on the 2nd test because they would be wiped out before being created.

You can load multiple fixtures by including them on the same line separated by commas.

fixtures :users, :losers, :bruisers, :cruisers

Fixtures are basically Hash objects. As mentioned in point #3 above, you can access the hash object directly because it is automatically setup as a local variable of the test case.

...
  fixtures :users

  def test_user
    # this will return the Hash for the fixture named david
    users(:david)

    # this will return the property for david called id
    users(:david).id
  end
...

But, by there’s another side to fixtures… at night, if the moon is full and the wind completely still, fixtures can also transform themselves into the form of the original class!

Now you can get at the methods only available to that class.

...
  fixtures :users

  def test_user
    # using the find method, we grab the "real" david as a User
    david = users(:david).find

    # an now we have access to methods only available to a User class
    email( david.girlfriend.email, david.illegitimate_children )
  end
...