Testing is a controversial topic. Some people love it, some hate it. Should one use TDD to build their product? Should one test at all? Which framework is the best? This post is going to sidestep these controversies. I’m assuming that you recognize the merits of testing, TDD when it’s convenient and makes sense, and have chosen to use RSpec. With that out of the way…

Unit testing should be simple, right?

There are several levels of abstraction a developer can choose to test their code at. At the unit level (Rails models and POROs), view tests, integration tests (controller and request specs), etc. Testing at the unit level is easy at first. Objects have small, discreet methods that don’t have to know about too much state. Life is good. But when your app becomes even a bit bigger and adds dependencies between the objects you have created, testing each piece becomes tedious. Questions like this often go through my head:

What does the object under test need to know about the entire system in order to function?

Here’s an example:

require 'spec_helper'

describe User do
  let(:user) { create(:user) }

  describe '#gets_free_sandwich?' do

    it 'should get a free sandwich' do
      user.expensive_calculation

      expect(user.gets_free_sandwich?).to eq(true)
    end

  end
end

We have a seemingly innocuous looking test here. We want to test that the user is eligible for a free sandwich. What could cause some frustration here is the #expensive_api_call. It could slow down our tests, or it may not even be implemented yet! In the latter case, we’re forced to go down the rabbit hole of implementing the other behavior before we can test #gets_free_sandwich?. It would be great to be able to implement tests for the #gets_free_sandwich? without having to worry about the implementation of its dependencies. Enter method stubbing.

Method stubbing allows us to focus on the implementation on the object we’re currently testing and lets us assume that the other parts of the system are implemented and working correctly.

require 'spec_helper'

describe User do
  let(:user) { create(:user) }

  describe '#gets_free_sandwich?' do

    it 'should get a free sandwich' do
      allow(user).to receive(:expensive_calculation) { 5 }

      expect(user.gets_free_sandwich?).to eq(true)
    end

  end
end

Caveats

Things aren’t all great. Method stubbing makes it easier to test isolated parts of your app, but doesn’t account for the functionality of the entire system. Integrations tests should be used in tandem with isolated unit tests in order to be confident that your code is actually working from end to end.