RSpec Mocks and `instance_double`

RSpec is a testing framework for Behaviour Driven Development that is composed of multiple libraries. In my current job, for testing Ruby on Rails application, we use rspec-rails gem that installs also all other libraries: rspec-core, rspec-exceptations, rspec-mocks and rspec-support.

RSpec Mocks

RSpec Mocks is a part of RSpec and it is

Test double framework, providing multiple types of fake objects to allow you to tightly control the environment in which your specs run.

A test double is an object used for stubbing another object in test environment. To create one there is double method:

user = double("user")

Let’s consider a following User class:

class User
  def initialize(name:)
    @name = name
  end

  def name
    @name
  end
end

And a following spec for testing last_name method which doesn’t exists:

describe User do
  let(:double_user) { double(User, name: "John") }

  describe "#last_name" do
    it "returns user last name" do
      expect(User).to receive(:new).and_return(double_user)
      expect(double_user).to receive(:last_name)

      User.new(name: double_user.name).last_name
    end
  end
end

Surprisingly, the spec passed, but it shouldn’t have as the method is not implemented.

Solution

In addition to double method there is also instance_double one. The method verifies doubles against real objects:

Most of the time you will want some confidence that your doubles resemble an existing object in your system. Verifying doubles are provided for this purpose. If the existing object is available, they will prevent you from adding stubs and expectations for methods that do not exist or that have an invalid number of parameters.

describe User do
  let(:double_user) { instance_double(User, name: "John") }

  describe "#last_name" do
    it "returns user last name" do
      expect(User).to receive(:new).and_return(double_user)
      expect(double_user).to receive(:last_name)

      User.new(name: double_user.name).last_name
    end
  end
end

The above spec returns a correct error:

Failure/Error: expect(double_user).to receive(:last_name)
  User does not implement: last_name

Summary

Anytime you stub real object using RSpec it is a better solution to use instance_double over double. It is a stricter alternative that checks if the methods being stubbed are actually present on the underlying object.

This dual approach allows you to move very quickly and test components in isolation, while giving you confidence that your doubles are not a complete fiction.

 

Igor Springer

I build web apps. From time to time I put my thoughts on paper. I hope that some of them will be valuable for you. To teach is to learn twice.