Mocks & Spies in Minitest
For one of our smaller ruby projects we didn’t want to bring in all of rspec, so we decided to go with minitest as our unit test runner and assertion library. Doing mocks and spies in minitest proved a little bit challenging to get the hang of initially but it’s actually quite easy. This article will explain how to do it.
First, let’s consider two classes - one helper class which we will be mocking out, and the other is the class under test.
class MyHelperClass
def upcase_instance(value)
# value.upcase
raise "Fail the test if the real thing is called"
end
def self.upcase_class_method(value)
# value.upcase
raise "Fail the test if the real thing is called"
end
end
class MyClassUnderTest
def use_upcase_instance(value)
helper_class = MyHelperClass.new
helper_class.upcase_instance(value)
end
def use_upcase_class_method(value)
MyHelperClass.upcase_class_method(value)
end
end
Note that if the real helper class’s methods are called the test will fail to illustrate that we’re doing the correct thing. In your test file you wil need to bring in the necessary require
s for minitest:
require "minitest/autorun"
require "minitest/mock"
The core aspect of mocking in minitest comes from Minitest::Mock
’s expect
method coupled with the stub
method on whatever you’re mocking. The expect method signature looks like this:
expect(name, retval, args = [], &blk) ⇒ Object
# Expect that method name is called, optionally with args or a blk, and returns retval.
So we can mock out an instance method like so:
mock = Minitest::Mock.new # Create a new mock
mock.expect(:upcase_instance, "FOURTYTWO", ["FoUrTyTwO"]) # Expect the upcase_instance method to be called with one argument "FoUrTyTwO" and force it to return the value "FOURTYTWO"
MyHelperClass.stub(:new, mock) do # Swap out the real implementation of the "new" method of MyHelperClass to return our mock
MyClassUnderTest.new.use_upcase_instance("FoUrTyTwO") # Use the method. The expectation has already been set in the previous line
end
There is an additional library you can add called bogdanvlviv/minitest-mock_expectations which can make this a lot simpler if you find yourself writing a lot of mocks and spy assertions. With it we can rewrite the above as
assert_called_on_instance_of(MyHelperClass, :upcase_instance, ["FoUrTyTwO"], returns: "FOURTYTWO") do
MyClassUnderTest.new.use_upcase_instance("FoUrTyTwO")
end
And for a class method you write
assert_called(MyHelperClass, :upcase_class_method, ["FoUrTyTwO"], returns: "FOURTYTWO") do
MyClassUnderTest.new.use_upcase_class_method("FoUrTyTwO")
end
That’s it!