The difference between Mocks, Stubs, and Spies
Tags: testing, ruby, rspec
Published: 2020-09-06 03:23:03

This post describes the difference between Mocks, Stubs, and Spies, and I use RSpec to show examples.

I hope this helps you write excellent tests!

## TL;DR

In [this great article](https://martinfowler.com/articles/mocksArentStubs.html), Mr. Martin Fowler says:

> Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.

> Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

> Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

## See the difference with RSpec

### Stubs

For example, if some method depends on a third-party API, it's not good to request it whenever testing.
In that case, stubs are very useful.

```ruby
class SomeClient
  def request
    body = some_lib.request
    JSON.parse body
  end

  private

  def some_lib
    @some_lib ||= SomeLib.new
  end
end
```

```ruby
describe 'SomeClient#request'
  before do
    allow(client).to receive(:some_lib).with(no_args).and_return(some_lib)
    allow(some_lib).to receive(:request).with(no_args).and_return(expected_boby)
  end

  let(:client) { SomeClient.new }
  let(:some_lib) { instance_double(SomeLib) }
  let(:expected_body) { ... }
  let(:expected_json) { JSON.parse expected_body }

  subject { client.request }

  it { is_expected.to eq expected_json }
end
```

We can avoid calling the API actually by stubs.

### Mocks

In the above stub example, if the client actually calls the API many times, it's completely unexpected.

If we want to ensure whether the `some_lib` method is invoked expectedly, using mocks is more suitable.

```ruby
describe 'SomeClient#request'
  before { allow(client).to receive(:some_lib).with(no_args).and_return(some_lib)  }

  let(:client) { SomeClient.new }
  let(:some_lib) { instance_double(SomeLib) }
  let(:expected_body) { ... }
  let(:expected_json) { JSON.parse expected_body }

  subject { client.request }

  it do
    expect(some_lib).to receive(:request).with(no_args).and_return(expected_boby).once # Ensure it's really called expectedly.
    is_expected.to eq expected_json
  end
```

If `SomeLib#request` is called unexpectedly (wrong args, called many times, etc.), the test will fail.

### Spies

In `rspec-mocks`, though it's a bit difficult to clarify the difference between mocks and spies...

RSpec documentation says:

> Message expectations put an example's expectation at the start, before you've invoked the code-under-test. Many developers prefer using an arrange-act-assert (or given-when-then) pattern for structuring tests. Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using have_received.

The above example would become with spies like:

```ruby
describe 'SomeClient#request'
  before { allow(client).to receive(:some_lib).with(no_args).and_return(some_lib)  }

  let(:client) { SomeClient.new }
  let(:some_lib) { instance_spy(SomeLib) } # It's OK to use `instance_double` instead, I describe the difference afterward.
  let(:expected_body) { ... }
  let(:expected_json) { JSON.parse expected_body }

  subject { client.request }

  it do
    is_expected.to eq expected_json
    expect(some_lib).to have_received(:request).with(no_args).and_return(expected_boby).once
  end
end
```

It might be more intuitive.

After `subject` is called, the test checks whether `SomeLib#request` is really called expectedly.

`instance_spy(SomeLib)` is similar to `instance_double(SomeLib)`. The difference is:

- `instance_double`
  - Can stub methods.
  - If you try to stub the undefined method, it would raise an error.
- `instance_spy`
  - Mix stubs and real objects.
  - If some real method is not stubbed, the real one would be called.

## Summary

Let's clarify the difference and write excellent tests!

## Reference

- https://martinfowler.com/articles/mocksArentStubs.html
- https://martinfowler.com/articles/mocksArentStubs.html