How do I test Rails migrations?

Peter Marklund has an example gist of testing a migration here: https://gist.github.com/700194 (in rspec).

Note migrations have changed since his example to use instance methods instead of class methods.

Here’s a summary:

  1. Create a migration as usual
  2. Create a file to put your migration test in. Suggestions: test/unit/import_legacy_devices_migration_test.rb or spec/migrations/import_legacy_devices_migration_spec.rb
    NOTE: you probably need to explicitly load the migration file as rails will probably not load it for you. Something like this should do: require File.join(Rails.root, 'db', 'migrate', '20101110154036_import_legacy_devices')
  3. Migrations are (like everything in ruby), just a class. Test the up and down methods. If your logic is complex, I suggest refactoring out bits of logic to smaller methods that will be easier to test.
  4. Before calling up, set up some some data as it would be before your migration, and assert that it’s state is what you expect afterward.

I hope this helps.

UPDATE: Since posting this, I posted on my blog an example migration test.

UPDATE: Here’s an idea for testing migrations even after they’ve been run in development.

EDIT: I’ve updated my proof-of-concept to a full spec file using the contrived example from my blog post.

# spec/migrations/add_email_at_utc_hour_to_users_spec.rb
require 'spec_helper'

migration_file_name = Dir[Rails.root.join('db/migrate/*_add_email_at_utc_hour_to_users.rb')].first
require migration_file_name


describe AddEmailAtUtcHourToUsers do

  # This is clearly not very safe or pretty code, and there may be a
  # rails api that handles this. I am just going for a proof of concept here.
  def migration_has_been_run?(version)
    table_name = ActiveRecord::Migrator.schema_migrations_table_name
    query = "SELECT version FROM %s WHERE version = '%s'" % [table_name, version]
    ActiveRecord::Base.connection.execute(query).any?
  end

  let(:migration) { AddEmailAtUtcHourToUsers.new }


  before do
    # You could hard-code the migration number, or find it from the filename...
    if migration_has_been_run?('20120425063641')
      # If this migration has already been in our current database, run down first
      migration.down
    end
  end


  describe '#up' do
    before { migration.up; User.reset_column_information }

    it 'adds the email_at_utc_hour column' do
      User.columns_hash.should have_key('email_at_utc_hour')
    end
  end
end

Leave a Comment