Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test Rails migrations?

I want to test that certain conditions hold after running a migration I've written. What's the current best way to do that?

To make this concrete: 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. None of my existing tests will catch that, because they all start with a fresh database and add new data, which will have the default. But if I push to production, I know things will break, and I want my tests to tell me that.

I've found http://spin.atomicobject.com/2007/02/27/migration-testing-in-rails/, but haven't tried it. It's very old. Is that the state-of-the-art?

like image 421
XZVASFD Avatar asked May 21 '11 01:05

XZVASFD


People also ask

How can I check my Rails migration status?

If you need a bash one-liner to determine whether to run a migration or not (e.g., only migrate in a Heroku release phase command when there is a pending migration), this works: (rails db:migrate:status | grep "^\s*down") && rails db:migrate || echo "No pending migrations found."

How do I run a migration in Rails?

Rails Migration allows you to use Ruby to define changes to your database schema, making it possible to use a version control system to keep things synchronized with the actual code. Teams of developers − If one person makes a schema change, the other developers just need to update, and run "rake migrate".


1 Answers

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 
like image 57
Amiel Martin Avatar answered Sep 22 '22 19:09

Amiel Martin