Mocks With Minitest

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

On this page

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

Prerequisites

  1. Basic familiarity with Bundler.
  2. Familiarity with reading tests written in RSpec.
  3. Familiarity with the Pry Gem.

Getting started

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

$ 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, 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:

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:

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:

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

Photo credit: benostrower