Get more articles like this about using Rails Like A Pro

Don’t mock what you don’t own

When I started to isolate my tests from it’s dependencies, I noticed there were times when this didn’t always lead to better tests. Faster? Yes. Better? That’s perhaps a little too subjective. How about brittle? Yes.

You see, in blindly following this rule, I ended up violating another one…

Don’t mock what you don’t own.

See dependency inversion principle for more info.

Basically, if your code has a dependency that you don’t control, then you shouldn’t mock it because you can’t guarantee the interface will remain constant.

This bites the worst when a library you depend upon, eg. Active Record, changes but you’ve mocked it in your tests and remain blissfully unaware that your code is broken.

How about this example…

describe Post do
  describe '.published' do
    let(:post) { double 'post' }

    before do
      Post.stub_chain(:where, :not).
        with(published_at: nil).and_return(post)
    end

    it 'returns posts with a published_at timestamp' do
      expect(Post.published.to_a).to eq [post]
    end
  end
end

It may be tempting to write a test like this. It’s fast, it tests the scope is defined correctly, so what’s the matter with it?

The main issue is that now, if anything about scopes change in Active Record, this test will continue to pass, even if things change. In this particular instance, the finder methods I’ve stubbed are Rails 4 specific but this test will actually pass on Rails 3 because I’ve stubbed out the required methods.

A more useful approach in terms of ensuring your code works is this…

require 'active_record_spec_helper'

describe Post do
  describe '.published' do
    let!(:post) { FactoryGirl.create :post }

    it 'scopes by published_at' do
      expect(Post.published.to_a).to eq [post]
    end
  end
end

It will be slower. We’re creating a real object in the db and then querying the db for results. When you think about it though, that is actually what we want to test in this case.

This is about testing the interaction between our app and the database, so it makes sense that it should actually interact.

In summary, in our pursuit of faster tests, sometimes it’s useful to take a step back and consider the purpose of the test we’re writing. Maybe complete isolation from it’s dependencies will actually be detrimental in it’s usefulness.

Are you interested in writing faster tests? Sign up to my newsletter for bi-weekly tips on Faster Tests With Rails

* indicates required