Notes to self

Reusing Rails test fixtures for db:seed

There are several reasons I like Rails’ fixtures for testing. One such reason is that modeling a small world gives you instant data for seeding your database.

If you always skip fixtures for factories when working with Rails, you are missing out!

What are fixtures? There are YAML definition of your database data which you can use in all your tests:

# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

<% password = "XuRaT7tSp" %>

joe:
  name: Joe
  email: <%= Faker::Internet.unique.email %>
  encrypted_password: <%= Devise::Encryptor.digest(User, password)  %>
  admin_clearance: 5
  confirmed_at: <%= DateTime.now %>

chris:
  name: Chris
  email: <%= Faker::Internet.unique.email %>
  encrypted_password: <%= Devise::Encryptor.digest(User, password)  %>
  admin_clearance: 4
  confirmed_at: <%= DateTime.now %>

Once defined, they’ll always be there for you at your disposal:

# frozen_string_literal: true

require "test_helper"

class UserTest < ActiveSupport::TestCase
  setup do
    @owner = users(:joe)
    @owners_team = teams(:owners)
    ...
  end

  test "#email_confirmed?" do
    assert_not @owner.email_confirmed?
    @owner.confirmed_at = DateTime.now
    assert @owner.email_confirmed?
  end

The main disadvantage of fixtures is indirection, the main advantage is speed. But there is more than the speed argument to it.

One small, but pretty neat thing is to reuse fixtures for your development data. Rails even have a task for it:

$ rails db:fixtures:load

This works automatically, but note that you must ensure that test dependencies used while defining fixtures now have to be moved to development group in your Gemfile. This is the case of the Faker gem above.

You can also be more specific and load only the tables you want:

$ rails db:fixtures:load FIXTURES=users,teams

Now the obvious next step would be to combine with some custom seed data, perhaps a user you can log in with:

$ rails db:fixtures:load db:seed

If you always want to load your fixtures for seeding the database, I recommend to invoke the task from the db/seeds.rb file:

# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
#   movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
#   Character.create(name: "Luke", movie: movies.first)

# Start with fixtures
Rake.application["db:fixtures:load"].invoke

# Add few more models...
User.create(...)

The task will work since Rails is loaded at this point.

And that’s it, really. One line of code for plenty of developer productivity.

Check out my book
Interested in Ruby on Rails default testing stack? Take Minitest and fixtures for a spin with my latest book.

Get Test Driving Rails and make your tests faster and easier to maintain.

by Josef Strzibny
RSS