Search code examples
ruby-on-railsrspec-rails

How temporarily disable "needs_migration?" check when testing migration?


I've written the spec to test my migration but when I run it I got an error:

ActiveRecord::PendingMigrationError:

Migrations are pending. To resolve this issue, run:

    bin/rake db:migrate RAILS_ENV=test

I've tried to disable the migration check in the before section but that check is running before all tests.

How to disable the migration check for testing purposes?


Solution

  • Testing Rails migration is a bit of a pain so I would rather step back and think about if this needs to be in a Rails migration / tested in a Rails migration.

    There are basically two different types of migrations

    Schema migrations

    Use mostly Rails built in functions. Unless you do some handcrafted SQL I wouldn't bother testing this and trust the framework here.

    Data migrations

    Data migrations are used to backfill or change data. As data is one of your most valuable assets and loosing or corrupting it is very painful I would definitely recommend to write tests for data migrations.

    As mentioned, testing migrations is a bit of a pain so I would try to abstract the data migration code in it's own (service) class. Something like

    class DataMigration::UpdateUsername
      def self.run
        new.run
      end
    
      def run
        User.all do |batch|
          user.update(name: user.name.capitalize)
        end
      end
    end
    

    You can now test the data migration like a normal class like this:

    it 'does capitalize the name' do
      user = create(:user, name: 'name')
    
      DataMigration::UpdateUsername.run
    
      expect(user.reload.name).to eq('NAME')
    end
    

    Now we can use this class in our Rails migration or e.g. just use it in a Rake task. Using it in a Rake task also has the advantages that we can pass in parameters, run several data migrations in parallel (e.g. you have a large data set) or even in a background job which you can't in a Rails migration.

    Example

    class DataMigration::UpdateUsername
      def initialize(start_id:, finish_id:)
        @start_id = start_id
        @finish_id = finish_id
      end
    
      def run
        User.find_in_batches(start: start_id, finish: finish_id) do |batch|
          batch.each do |user|
            user.update(name: user.name.capitalize)
          end
        end
      end
    end
    

    Now we can create a custom task for this

    namespace :db do
      desc "Runs user data migration"
      task :update_user, [:start, :finish] do |task, args|
        DataMigration::UpdateUsername.new(start_id: args[:start], finish_id: args[:finish])
      end
    end
    
    rake db:update_user[0, 10000]
    rake db:update_user[10000, 20000]
    # ...