Here’s how to test dependencies with mocks
Tags: ruby, testing, tdd
Published: 2020-02-29 14:58:16

---
title: Here’s how to test dependencies with mocks
published: true
description: There’s a straightforward process for dealing with dependencies
tags: ruby, testing, tdd
---

In this post I’ll present a straightforward process for testing dependencies. No matter which language, framework or unit testing tool you’re using, you can rely on this process to fully test your dependency use.

I’m using Ruby for my code samples but they apply in just about every modern programming language.

Let’s look at an example of a class with a dependency.

```ruby
class FooRepository
  def save(id, docoment)
    # ...
  end
end

class Foo
  def initialize(repository)
    @repository = repository
  end

  def do_something(document)
    @repository.save('Foo', document)
  end
end
```

In this example `repository` is the dependency. We’re going to write tests for the `do_something` function.

> A **dependency** of a code unit (an object, or a function) is another code unit that is executed as part of the containing code unit’s execution.

In an object-oriented environment we often see dependencies passed into the class constructor, as in the example above. But functions can have dependencies too.

In this case, `Foo` does not *create* an instance of `repository`. It does not control the **lifetime** of the dependency. In fact, it knows nothing more about `repository` than these two facts:

 * it has a method named `save` that takes two arguments: a string and a `document`
 * it returns either nil or something else (we don’t care what else)

And it’s these two facts that lead our testing.

> Every time we test a dependency, we need at least these two tests:
> 1. that it was invoked with the right parameters
> 2. that the return value was used correctly

# Test 1: the dependency is invoked with the right parameters

This example uses some special RSpec syntax that you may not be familiar with, which I’ll explain after:

```ruby
require 'foo'

RSpec.describe Foo do

  let(:repository) { spy }

  subject do
    described_class.new(repository)
  end

  it 'calls repository#save with the name and document' do
    subject.do_something(:document)
    
    expect(repository).to have_received(:save)
      .with('Foo', :document)
  end
end
```

If you’re not familiar with RSpec, here are three points that should help explain this example.


  1. Most importantly, `spy` is a function call that creates a new mock object.
  2. `described_class` and `subject` are conventional ways to refer to the **subject under test**. In this case `described_class` is `Foo` and `subject` is an instance of `Foo`.
  3. More subtly, `:document` is a symbol that allows us to give a name to a value _but we don’t care about the value itself_. More on this later.


Essentially what we do is place a **spy object**&mdash;a type of test double which records its invocations&mdash;where the dependency should be. We then invoke the method we’re testing, and then check the spy’s records to ensure that it was indeed invoked.


## Test 2: the dependency’s return value is used correctly

If you’re unfamiliar with Ruby, you may look at the code above and think that we simply throw away the return value. But this isn’t true. In Ruby, the returned value of a function is the value of the last expression, which in this case is the returned value of the call to `save`. There implicity behavior here that we’re relying on.

To test, we **stub** a return value using the `allow` function.

```ruby
it 'returns the result of calling repository#save' do
  allow(repository).to receive(:save).and_return(:saved_document)

  expect(subject.do_something(:document)).to be :saved_document
end
```

Notice again that it’s not the value of the return value that’s important, but the _identity_. We want to check that it’s the same value we passed in. So we can use a symbol, `:saved_document` to do that without having to define an extra variable.

## Modification 1: Protecting against dependency signature changes in dynamic languages

Imagine now that we rename the `save` method of `repository` to `save_document`, but we forget to update `Foo`: it still calls `save`:

```ruby
class FooRepository
  # renamed from save
  def save_document
    ...
  end
end

class Foo
  def do_something(document)
    @repository.save('Foo', document) # ERROR: still calls save!
  end
end
```

Because Ruby doesn’t do any static analysis of our functions, our tests will still pass even though `save` no longer exists. This code will break at runtime.

Wouldn’t it be great if we could make our tests fail when the dependency method signature changes?

```
Failure/Error:
  allow(repository).to receive(:save).and_return(:saved_document)

  the FooRepository class does not implement the instance method: save
```

We can do that by replacing the call to `spy` with `instance_spy`, which produces a **verifying double**:

```ruby
let(:repository) { instance_spy(FooRepository) }
```

This isn’t unique to RSpec: all good mocking frameworks will support this functionality.

> A verifying double will break your test if you try to stub a method that doesn’t exist on the original class.

## Modification 2: Add a test for every new branch

Now imagine that `do_something` looks like this:

```ruby
def do_something(document)
  result = @repository.save('Foo', document)
  :ok if result
end
```

This code changes the return value of `do_something` depending on the returned value of save. If `save` returns anything, then the result is `:ok`, otherwise the return value is `nil`. Because of that, we now need _two_ tests for the return value; one test for each branch.

```ruby
it 'returns :ok when repository#save returns something' do
  allow(repository).to receive(:save).and_return(:saved_document)

  expect(subject.do_something(:document)).to be :ok
end

it 'returns nil when repository#save returns nil' do
  allow(repository).to receive(:save).and_return(nil)

  expect(subject.do_something(:document)).to be_nil
end
```

## Systemizing testing

It’s important to systemize your testing. In this post I’ve shown you one way you can do that: every time you want to test a dependency, you can follow this process to ensure your code is correctly covered.