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
- Basic familiarity with Bundler.
- Familiarity with reading tests written in RSpec.
- 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.
- The test of what we would actually expect.
- A test that mocks out an instance method
Person.new(...).describe_personto return different values. - A test that mocks out a class method
Person.descriptionto 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