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
requires for minitest:
require "minitest/autorun" require "minitest/mock"
The core aspect of mocking in minitest comes from
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