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

I just create an instance of the class, then call up or down on on it.

For example:

require Rails.root.join(
  'db',
  'migrate',
  '20170516191414_create_identities_ad_accounts_from_ad_account_identity'
)

describe CreateIdentitiesAdAccountsFromAdAccountIdentity do
  subject(:migration) { described_class.new }

  it 'properly creates identities_ad_accounts from ad account identities' do
    create_list :ad_account, 3, identity_id: create(:identity).id

    expect { suppress_output { migration.up } }
      .to change { IdentitiesAdAccount.count }.from(0).to(3)
  end
end

I made a migration that adds a column to a model, and gives it a default value. But I forgot to update all the pre-existing instances of that model to have that default value for the new column.

Based on this statement, you are just trying to test that an "old" model, has the default, correct?

Theoretically you are testing if rails works. I.e., "Does rails set a default value to a newly added column"

Adding a column and setting a default value will be there in the "old" records of your database.

So, you don't need to update the other records to reflect the default setting, then. In theory there is nothing to test, as rails has tested that for you. Lastly, the reason to use defaults is so that you don't have to update the previous instances to use that default, right?