# Mocks With Minitest

> A short overview on how you can mock both class methods and instance methods when dealing with Minitest.

- Canonical: https://dennisokeeffe.com/blog/2022-04-06-mocks-with-minitest
- Published: 2022-04-06
- Updated: 2022-04-06
- Tags: ruby

This post will overview how you can use Minitest mocks within your tests for both class and instance methods.

Source code can be found [here](https://github.com/okeeffed/demo-minitest-mocking)

## Prerequisites

1. Basic familiarity with [Bundler](https://bundler.io/).
2. Familiarity with reading tests written in [RSpec](https://rspec.info/).
3. Familiarity with the [Pry Gem](https://www.rubydoc.info/gems/pry/0.10.3/Pry).

## Getting started

We will create the project directory `demo-minitest-mocking` and use Bundler to initialize the project:

```s
$ mkdir demo-minitest-mocking
$ cd demo-minitest-mocking

$ mkdir lib test
$ touch lib/car.rb lib/person.rb test/test_car.rb Rakefile
```

As noted in [this blog post](https://medium.com/@mario_chavez/testing-rails-with-minitest-7b4f99d4fcb8), MiniTest ships with never versions of Ruby, so there may be no need for any other installation.

We are going to test some contrived classes where an instance of `Car` has a `Person` as a driver and calls a contrived class method `Person.describe` when calling its own method. Hopefully this will make more sense as we write out the two classes.

## Writing the Person class

In `lib/person.rb`, add the following:

```rb
class Person
  attr_reader :name, :age

  class << self
    def describe
      'I am the representation of a person'
    end
  end

  def initialize(name, age)
    @name = name
    @age = age
  end

  def describe_person
    [@name, @age]
  end
end
```

Effectively we are initializing the person with a `name` and `age` property, having a `describe_person` instance method return a tuple of the name and age, and we have also added a class method to describe the `Person` class.

Next, we need to write our code for the `Car` class.

## The Car Class

In `lib/car.rb`:

```rb
require 'person'

class Car
  attr_accessor :driver

  def initialize
    @driver = Person.new('John Doe', 30)
  end

  def describe_driver
    name, age = @driver.describe_person
    description = Person.describe

    [name, age, description]
  end
end
```

In this class, we always designate the driver as a `Person` instance. This is forced and contrived, but you will see how we can mock the `Person` class in the next section.

We also have a `describe_driver` instance method that returns a tuple of the driver's name, age, and the description of the `Person` class.

## Writing our tests

With how the current code works, we expect `Car.new.describe_driver` to always return `['John Doe', 30, 'I am the representation of a person']` because the code is hardcoded.

We will write a test to demonstrate this, but then the following two tests will demonstrate how we can mock them out.

In `test/test_car.rb`, add the test code:

```rb
require 'minitest/autorun'
require 'car'
require 'person'

describe Car do
  describe '#describe_driver' do
    it 'should return information on the driver provided' do
      result = Car.new.describe_driver
      expectation = ['John Doe', 30, 'I am the representation of a person']
      assert_equal(expectation, result)
    end

    # This test will mock the result of Person.new(...).describe_person()
    it 'should return information from the mocked person instance methods' do
      mock = MiniTest::Mock.new
      mock.expect(:describe_person, ['Jane Doe', 20])

      Person.stub(:new, mock) do
        result = Car.new.describe_driver
        expectation = ['Jane Doe', 20, 'I am the representation of a person']
        assert_equal(expectation, result)
      end

      mock.verify
    end

    # This test will mock the result of Person.description()
    # Note that you need to mock.expect(:call) for class methods
    it 'should return information from the mocked person class method' do
      mock = MiniTest::Mock.new
      mock.expect(:call, 'I am another representation of a person')

      Person.stub(:describe, mock) do
        result = Car.new.describe_driver
        expectation = ['John Doe', 30, 'I am another representation of a person']
        assert_equal(expectation, result)
      end

      mock.verify
    end
  end
end
```

We write three tests in the above.

1. The test of what we would actually expect.
2. A test that mocks out an instance method `Person.new(...).describe_person` to return different values.
3. A test that mocks out a class method `Person.description` to return a different description.

The important thing to note with the class methods is that you will mock `:call`, which itself is a little confusing when you compare how the mocks for instance methods work.

## Summary

Today's post demonstrated how to mock both class methods and instance methods in Minitest.

Admittedly, the code is very contrived and the Minitest mocking approach isn't very familiar if you are used to other test runners like Jest, but it is a useful reference for when you will need it.

## Resources and further reading

- [Minitest Mock docs](https://docs.seattlerb.org/minitest/Minitest/Mock.html)
- [Source code](https://github.com/okeeffed/demo-minitest-mocking)

_Photo credit: [`benostrower`](https://unsplash.com/@benostrower)_
