
I’ve never liked mock object frameworks. Especially in Java, they were cumbersome to set up and never let me do all the things I wanted to. I always found it quicker just to roll my own mock.
Even in Python I had adopted this practice. Recently, Pulp settled on the Mock framework (http://www.voidspace.org.uk/python/mock/), so I decided to give mock frameworks another shot.
Installation
Installation is nice and simple:
$ easy_install mock
Setup
Even simpler, there’s nothing global you have to do in your unit test setup. When you’re ready to use the mock, instantiate it and go.
Usage
Creating a mock instance is as simple as importing the package and instantiating it. There’s no need to provide any sort of description of what should or should not be supported by the object. For instance:
In [1]: import mock In [2]: m = mock.Mock() In [3]: m.foo() Out[3]: <Mock name='mock.foo()' id='22148496'>
That aspect alone is really cool. There’s no arcane setup I need to remember to do when writing my unit test. I want a mock, I create it, and go on with life.
Behavior
Of course, as cool as such little setup is, it also doesn’t really give us all that much on its own.
Modifications to the behavior of an individual method on the mock are done by setting attributes on the method itself. Again, there’s no setup step to let the mock know it should support a method. You just set the behavior on the method and the mock magically remembers it.
For example, to simulate a return value to the foo method above:
In [4]: m.foo.return_value = 'fus ro dah' In [5]: m.foo() Out[5]: 'fus ro dah'
All good unit tests should exercise error conditions too, which is done through the side_effect
attribute:
In [7]: m.foo.side_effect = Exception() In [8]: m.foo() --------------------------------------------------------------------------- Exception Traceback (most recent call last) /home/jdob/<ipython-input-8-518df19c1ba0> in <module>() ----> 1 m.foo() [snip]
Assertions
Chances are, your mock is being called deep within another method that’s being tested. You’ll want to know that the method being tested is actually calling the mock and that it’s passing the correct values. Again, this information is provided as attributes on the mock’s method object.
If I want to make sure my method is actually calling the mock, I can determine how many times a particular method has been invoked:
In [6]: m.foo.call_count Out[6]: 2
If I’m really careful in my tests, I want to make sure the proper values are being passed to the mock. This is especially important in Python where it’s all too easy to reverse the order of parameters in the invocation. This check is a bit tricker since it returns the call arguments in a tuple. The first entry in the tuple is any ordered arguments and the second is any keyword arguments.
In [12]: m.bar('wombat', 'zombie', blah='blargh') Out[12]: <Mock name='mock.bar()' id='23023760'> In [13]: m.bar.call_args[0] Out[13]: ('wombat', 'zombie') In [14]: m.bar.call_args[1] Out[14]: {'blah': 'blargh'}
Clean Up
If you’re doing multiple runs that use the same mock, you’ll want to reset the state of the mock between runs. The thing to remember here is that it doesn’t reset the return value or side effects of any methods you’ve configured. That’s important to keep in mind if your tests individually pass but when run as a suite start to fail; chances are you have a rogue side effect on a method that hasn’t been cleaned up.
Resetting the mock is done through the reset_mock
method on the mock itself:
In [15]: m.foo.call_count Out[15]: 3 In [16]: m.reset_mock() In [17]: m.foo.call_count Out[17]: 0
And Beyond
There’s obviously more to the Mock framework than I’ve outlined. I’ve covered the basics and things I use on a regular basis. Their documentation is pretty solid; in particular I have the Mock class documentation (http://www.voidspace.org.uk/python/mock/mock.html) bookmarked to quickly refer to.
Go forth and test