Introducing callee: matchers for unittest.mock
Posted on Sun 20 March 2016 in News
Combined with dynamic typing, the lax object model of Python makes it pretty easy to write unit tests that involve mocking some parts of the tested code. Still, there’s plenty of third party libraries — usually with “mock” somewhere in the name — which promise to make it even shorter and simpler. There’s evidently been a need here that the standard library didn’t address adequately.
It changed, however, with Python 3.3. This version introduced a new
unittest.mock
module which essentially obsoleted
all the other, third party solutions. The module, available also as a backport
for Python 2.6 and above, is flexible, easy to use, and offers most if not all of the requisite functionality.
Called it, maybe?
Well, with one notable exception. If we mock a function, or any other callable object:
# (production code)
class ProductionClass(object):
def foo(self):
self.florb(...)
# (test code)
something = ProductionClass()
something.florb = mock.Mock()
we have very limited capabilities when it comes to verifying how they were called. Sure, we can be extremely specific:
something.florb.assert_called_with('this particular string')
or very lenient:
something.florb.assert_called_with(mock.ANY) # works for _any_ argument
but there is virtually nothing in between. Suppose we don’t care too much what exact string the method was called with, only that it was some string that’s long enough? Things get awkward very fast then:
for posargs, kwargs in something.florb.call_args_list:
arg = posargs[0]
self.assertIsInstance(arg, str)
self.assertGreaterEqual(len(arg), 16)
break
else:
self.fail('required call to %r not found' % (something.florb,))
Yes, this is what’s required: an actual loop through all the mock’s calls1; unpacking tuples of positional and keyword arguments; and manual assertions that won’t even emit useful error messages when they fail.
Eww! Surely Python can do better than that?
Meet callee
Why, of course it can! Today, I’m releasing callee: a library of argument matchers
compatible with unittest.mock
. It enables you to write much simpler, more readable,
and delightfully declarative assertions:
from callee import String, LongerOrEqual
something.florb.assert_called_with(String() & LongerOrEqual(16))
The wide array of matchers offered by callee includes numeric, string, and collection ones. And if none suits your particular needs, it’s a trivial matter to implement a custom one.
Check the library’s documentation for more examples, or jump right in to the installation guide or a full matcher reference.
-
A loop is not necessary if we simulate
assert_called_once_with
rather thanassert_called_with
, but we still have to dig intomock.call
objects to eke out the arguments. ↩