As we add more features to our applications we inevitably have to refactor existing code. At one point I had to introduce polymorphic relation to a model. Previously it was simply this with Mongoid and Ruby on Rails

class Article
  belongs_to :user
end
class User
  has_many :articles
end

Now we change it to:

class Article
  belongs_to :author, polymorphic: true
end
class User
  has_many :articles, as: :author
end
class Team
  has_many :articles, as: :author
end

We also need to do a small data migration with mongoid_rails_migrations.

class MyMigration < Mongoid::Migration
  def self.up
    Article.all.update_all(author_type: 'User')
    Article.all.rename(user_id: :author_id)
  end
  def self.down
    Article.all.rename(author_id: :user_id)
    Article.all.unset(:author_type)
  end
end

Under the hood Rails uses author_id and author_type fields in Article model. But this post is not about polymorphic relations (read more here).

After implementing this change we need to update article.user to article.author. And we need to change Rspec tests and FactoryGirl factories.

When I was doing this in my application I realized that while I changed a few model / controller (core code) files I had to change LOTS of test files. I had pretty decent coverage (over 80%) but maintaining the tests became quite time consuming. The cost of refactoring tests became greater than the cost of refactoring the application.

The most common reason I had to change my tests is because I was explicitly calling this in my tests:

user = create(:user)
article = create(:article, user: user)

Even though I created association in the factories:

FactoryGirl.define do
  factory :user do
    ...
  end
end
FactoryGirl.define do
  factory :article do
    user
  end
end

The reason is I had to test methods on User and Article that expected specific instances and it was just too easy to create in the test (only one line) than to properly think through how to setup the factory. And I had numerous validations in my models that required data to be setup in a very specific manner.

One way to avoid this overhead when all we need is to save data to our DB is to do this:

it 'some test'
  article = Article.new(title: ..., body: ...)
  article.save(validate: false)
  # test here
end

Now data is persisted and we can test article methods (especially if we don’t care about user relationships).

A different technique to use stubs and doubles.

it 'some test'
  article = double('article')
  allow(article).to receive(:user).and_return User.new
end

This is best applied with small modular classes that follow single responsibility principle.

I use these approaches selectively because while I want to test my code in isolation faking too many things can lead to problems of their own.

Another important lesson to ease the refactoring pain is following Law of Demeter but that’s for a different blog post.