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:
“`ruby
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.