Mocks and stubs in ruby unit tests, a cheatsheet
Tags: ruby, testing, mocks, stubs
Published: 2019-08-14 11:50:52

Based on Sandi Metz's very precise explanations, I will try to give you the ultimate mnemonic (if you learn it) or cheatsheet (if you bookmark/copy it) to the question _what to test?_.

First, let's establish the terms I will use to describe the four options:

*Query:* is a method that returns data but does not change the state of the receiver object, i.e.: 
```ruby
>> a = [1,2,3]
=> [1, 2, 3] 
a.size
=> 3 
>> a
=> [1, 2, 3] 
```

*Command:* is a method that may or may not return a value, but it always changes the state of the receiver object.
```ruby
>> a = [1,2,3]
=> [1, 2, 3] 
>> a.pop
=> 3 
>> a
=> [1, 2] 
```
*Incoming*: is a method defined _in_ the class you are testing, and which behaviour you want to test on its unit test.

*Outgoing*: is a method defined _out_ of the class you are testing, and which behaviour you need to simulate but don't want to test on your unit test.

Ok, those are the definitions, now for the rules:

## 1. Incoming query: 

* Assert the return value of the method

## 2. Incoming command

* Assert the return value of the method (if there is one)
* Assert the change in state

You changed state might not be accesible via the public interface of your object, but as Xavier Noria says, if it's important enough to need a text, then it's worth using `.send()` or `instance_variable_get()` to check on it. Take advantage of ruby's advanced introspection capabilities.

## 3. Outgoing query

* Stub the response of the other class

The gist here is, you need some value provided by some external class but that class does not mind if you call it or not, as it states remains unchanged.

So, for your test, what you need is that value, and not the class that provides it, especially if it's not-yours, complicated to set up for answering a particular value, not under your control, etc.

This is how you stub a method call with Minitest's included stub system:

```ruby
  OtherKass.stub :query, "response" do
    assert_equal "expected_value", obj_under_test.tested_method
  end
```
 
## 4. Outgoing command

* Stub the response of the other class (if you need it)
* Mock the receiver object to check that the call is made

Sometimes, the call you make on other class has side-effects, especially _intended_ side-effects, like persistance, notifications, money-transfers, asset creation...

If this a unit test, how the other class manages it's state is none of your business, what you have to test is _that the call is made with the right arguments_.

It is the job of the other class tests to check your call as an "incoming command"

The toll Minitest provides us with for this predicament is the `Minitest::Mock` class. I has two methods of interest:

`.expect(...)`: what method and arguments must we verify (called before the exercise of the method under test)

`.verify`: Did the expected calls happened as specified? (called after you call your object's method, to see that it triggered the expected external call)

Let's see an example:

```ruby
# setup
@mock = Minitest::Mock.new
@mock.expect(:outgoing_command, "return_value", [String])

# exercise within your class
@mock.outgoing_command("foo") # => true

# verify at the end
@mock.verify  # => true
```
There are more elaborate details about how to use `.expect` [on the documentation](https://www.rubydoc.info/gems/minitest/Minitest%2FMock:expect)

The complex thing is usually to get your class to use the mock, which can be done via a previous stub, or dependency injection:

### With a previous stub

This is quite common, and not bad if you only have one external dependency with a very stable interface, but if you see yourself having several of these per test, try to decouple your classes and inject your dependencies like the example after this one.

Example taken from [Mocking in Ruby with Minitest](https://semaphoreci.com/community/tutorials/mocking-in-ruby-with-minitest)

```ruby
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test '#apply_subscription' do
    mock = Minitest::Mock.new
    mock.expect :apply, true

    SubscriptionService.stub :new, mock do
      user = users(:one)
      assert user.apply_subscription
    end

    assert_mock mock # New in Minitest 5.9.0
    assert mock.verify # Old way of verifying mocks
  end
end
```

### With Dependency injection
This pattern is not only easier to test, it is also easier to refactor when needing to change collaborator objects, maybe not at the same time for all usages, think _Adapter Pattern_ or feature flag or maybe A/B-test selected strategies.

```ruby
class UserGreeter
  def initialize(name:, notifier: Slack)
    @name = name
    @notifier = notifier # store replaceable collaborator object
  end

  def run
    msg = whatever(@name)
    @notifier.notify(msg) # use injected dependency by internal name
  end
end

# on the test
require 'test_helper'

class UserGreeterTest < ActiveSupport::TestCase
  test '#run' do
    mock = Minitest::Mock.new
    mock.expect :notify, true, [String]

    user = User.new(
      user_attrs.merge(subscription_service: mock) 
    )
    assert user.run

    assert_mock mock
  end
end
```

## TL;DR

And here as promised I leave you tha handy mnemonic table:

|            |    Query      |    Command      |
|-----------:|:-------------:|:---------------:|
| _incoming_ |   result      | state (+result) |
| _outgoing_ |    stub       |      mock       |