Rails has always made it easy to manage a database just for test data automatically cleared between tests. While there’s no denying this is tremendously useful, it has also pulled all of us into feeling that a test that touches the database (a huge third-party dependency) is somehow a unit test. One of the most valuable ways in which Ruby-on-Rails has supported automated testing is using easily created data accessible to all the tests in our system, no matter when or where we write those tests, using fixtures specified in a YAML file (YAML is a file format. The acronym stands for YAML Ain’t Markup Language). It’s sometimes hard for an experienced Rails programmer to remember just how exciting the YAML fixtures used to seem. We can just set up data once? In an easy format? And it’s always there? Amazing.

Over time, the infatuation with fixtures has dimmed, but they are still quick and easy to get data into our tests.

What’s a fixture?

Generically, a fixture is any known baseline state that can be made to exist at the beginning of a test. A fixed state’s existence makes it possible to write tests that make assumptions based on that particular set of data. In Rails, the term “fixture” refers to a specific mechanism baked into the framework to easily define a set of objects that will exist globally for all tests. These fixtures are defined in a set of YAML files that are automatically written to the database and converted to ActiveRecord objects at the beginning of each test run.

Fixture files directory

Each ActiveRecord model in our application can have an associated fixture file. That fixture file is named after the plural version of the model. So, if we wanted fixtures for our Projects and Task models, they would go to spec/fixtures/projects.yml and spec/fixtures/tasks.yml, respectively. (If you’re using Minitest, the directory would be test/fixtures/). If we use Rails generators to create our model, then a fixture file is created for us, with some boilerplate values for each attribute.

The fixture file is in YAML format, a data-description format often used as an easier-to-type alternative to XML. The details of YAML syntax are both ways outside this book’s scope and largely irrelevant to fixtures. YAML contains several advanced features that don’t concern us here.

Fixture entry

Each entry in a fixture file starts with an identifier, with the attributes for that entry subordinate to the identifier. Here’s a sample for Project:

runway:
  name: Project Runway
  due_date: 2016-12-18

book:   
  name: Write the book
  due_date: 2017-04-14

YAML syntax

YAML syntax is somewhat reminiscent of Python, both in the colon used to separate key/value pairs and indentation to mark each entry’s bounds. The line book: is outdented two spaces indicating to the YAML parser that a new entry has begun. Strings do not need to be enclosed in quotation marks (except for strings the YAML parser would find ambiguous, such as if the string value also contains a colon and space). It doesn’t hurt to add the quotation marks if they seem more readable, though.

Multiline strings

We can specify a multiline string by putting a pipe character ( | ) on the line with the attribute name. The multiline string can then be written over the next set of lines. Each line must be indented relative to the line with the attribute name. Once again, outdenting indicates the end of the string:

runway:
  name: Project Runway
  due_date: 2016-12-18
  description: |
    The most awesome project ever.
    It's really, really great.

The Rails fixture-creation process uses information in our database to coerce the values to the proper type. We write dates in SQL format (yyyy-mm-dd), though any format readable by Ruby’s Date.parse() will work.

Names fixtures

The identifier that introduces each record is then used to access the individual fixture entry within our tests. Assuming that this is the Project class, we’d be able to retrieve these entries throughout our test suite as projects(:runway) and projects(:book), respectively. Unless it’s enjoyable to figure out what’s special about projects(:project_10), We recommend meaningful entry names, especially for entries that expose special cases like this: projects(:project_with_no_due_date).

Models fixtures

The YAML data is converted to a database record directly in SQL, without using ActiveRecord#new or ActiveRecord#create. (To be clear, when we use the data in our tests, the objects we get are ActiveRecord models. ActiveRecord is bypassed only when the fixture is first converted to a database record.) This means we can’t use the model’s arbitrary methods as attributes in the fixture the way we can in a create call.

Attributes fixtures

Fixture attributes have to be either actual database columns or ActiveRecord associations explicitly defined in the model. They can not be arbitrary setter methods defined in the model’s code. Removing a database column from our model and forgetting to take it out of the fixtures results in our test suite erroring out when loaded. The fixture-loading mechanism also bypasses any validations we’ve created on our ActiveRecord, meaning there is no way to guarantee the validity of fixture data on load, short of explicitly testing each fixture yourself.

Relationships fixtures

We don’t need to specify the id for a fixture (although we can if we want). If we do not explicitly specify an id, the id is generated for us based on the entry’s YAML identifier name. If we allow Rails to generate these id values, we get a side benefit: an easy way of specifying relationships between fixture objects. If our two model classes have an explicitly defined ActiveRecord relationship, we can use one object’s fixture identifier to define the other object’s relationship. In this snippet from a potential tasks.yml, we’re defining the task as having a relationship with the project defined as projects(:book):

chapter:
  title: Write a chapter
  project: book

If the relationship is has_many, the relationship’s multiple values can be specified as a comma-delimited list. This is true even if the two objects are in a has_and_belongs_to_many relationship via a join table, although a has_many :through relationship needs to have the join model entry explicitly specified.

Embedded Ruby (ERB) files fixtures

Fixture files are also interpreted as ERB (Embedded Ruby) files, which means we can have dynamic attributes like this:

runway:
  name: Project Runway
  due_date: <%= 1.month.from_now %>

Or we can specify multiple entries dynamically, like this:

<% 10.times do |i| %>
task_<%=i%>:
  name: "Task <%= i %>"
<% end %>

Loops avoidance

In the second case, notice that the identifier still needs to be at the leftmost column. We can’t indent the inside of the block the way normal Ruby style would suggest. Try not to loop inside fixture files.It gets confusing really quickly, largely because the record names are generated dynamically, making the file hard to read. (The only exception is if we don’t intend to refer to any individual record directly, only via finders. Then the naming confusion is less of an issue). If we need dynamic data functionality like this, we’re probably better off with a factory tool.

Get hands-on with 1200+ tech skills courses.