We’ve just released a new major version of kgb, a Python library for creating function spies in unit tests. This is a very handy tool for helping craft unit tests in Python applications.
kgb 2.0 introduces support for Python 3.6, improves argument checking, and removes the need for a special .spy
attribute on standard functions.
What are function spies?
Function spies allow you to listen for when functions are called, what parameters they were passed, what value they returned or exception they raised, and allow you to disable the function’s normal behavior and optionally replace it with your own. This is a popular feature of Jasmine, a testing framework for JavaScript.
They’re particularly useful when working with third-party libraries whose behavior you cannot normally change. For example, your project might call a function in a library that in turn calls out to a HTTP server, which might be problematic for your unit test. With kgb, you can simply spy on urllib2.urlopen
and return a custom result.
For example:
import logging
from unittest import TestCase
from urllib2 import urlopen
from kgb import SpyAgency
class MyTests(SpyAgency, TestCase):
def test_http_request(self):
def _fake_urlopen(opener, *args, **kwargs):
self.assertEqual(url.get_full_url(),
'https://example.com/123/')
class FakeResult(object):
def read(self):
return 'Your fake payload goes here!'
return _FakeResult()
self.spy_on(urlopen, call_fake=_fake_urlopen)
# Imagine that this function makes an HTTP request to
# https://example.com/123/ and logs a message.
some_function_that_does_urlopen()
self.assertTrue(urlopen.called)
self.assertTrue(logging.info.called_with(
'Fetching something from an API'))
What’s new in kgb 2.0?
Better, more consistent spies
We removed the distinction between spying on standard functions and methods on classes. This used to be treated differently. Previously, you could call spy functions like .called_with()
and access attributes like .last_call
directly on the method, but for functions, you had to use .spy.called_with()
and .spy.last_call
. We also kept plain functions mostly intact, but replaced methods on a class with a method-like object designed to intercept calls and mimic the method’s signature. That meant things were different depending on what you were spying on.
We now keep the methods where they are, bringing the spy functions and attributes onto the spied functions directly. We also use a special bytecode injection process for all spying operations (it’s very complicated, but awesome).
What does this ultimately mean? Well, it means if you had code from older versions that looked like this:
self.assertTrue(my_func.spy.called)
You can trim off the .spy
part:
self.assertTrue(my_func.called)
It also means that we’re spying at a lower level than before for methods on classes, helping to prevent problems with code that’s sensitive to methods being replaced.
And it gives us Python 3.6 support.
Python 3.6 and PyPy support
See, there it is!
kgb 2.0 now fully works with Python 3.6. And as a bonus, PyPy as well.
More flexible and descriptive argument checks
called_with()
now lets you check positional arguments by specifying their names as keyword arguments, and doesn’t require that you check for all arguments passed to the function. For example, if you have this method:
def my_func(a, b, c=123):
pass
you can inspect the calls with:
self.assertTrue(my_func.called_with(b=True))
Hand-holding when things go wrong
If something goes wrong in your test suite and your spy fails to unregister at the end of the test, you could get some pretty confusing assertion errors in older versions of kgb. Same if you accidentally try spying on the same function twice in your tests.
In kgb 2.0, we check for this and present a very helpful error showing you exactly where the spy was originally set so you don’t have to hunt it down yourself.
Learn more about kgb
We’re biased, but this is a really nifty library, and has made our lives so much easier. We have full documentation up on GitHub showing all the ways you can work with spies, along with a FAQ.
Installation is easy. Just run:
$ pip install kgb
kgb supports Python 2.6 through 2.7 and 3.4 through 3.6, along with new, experimental support for PyPy.
If you find kgb useful, please tell others about it, and give it a star on GitHub.
Read More