Mocking is a critical technique for automated testing. It allows you to isolate the code you are testing, which means you test what you think are testing. It also makes tests less fragile because it removes unexpected dependencies.
However, creating your own mocks by hand is fiddly, and some things are quite difficult to mock unless you are a metaprogramming wizard. Thankfully Michael Foord has written a mock module, which automates a lot of this work for you, and it’s awesome. It’s included in Python 3, and is easily installable in Python 2.
Since I’ve just written a test case using mock.patch, I thought I could walk through the process of how I approached writing the test case and it might be useful for anyone who hasn’t come across this.
It is important to decide when you approach writing an automated test what level of the system you intend to test. If you think it would be more useful to test an orchestration of several components then that is an integration test of some form and not a unit test. I’d suggest you should still write unit tests where it makes sense for this too, but then add in a sensible sprinkling of integration tests that ensure your moving parts are correctly connected.
Mocks can be useful for integration tests too, however the bigger the subsystem you are mocking the more likely it is that you want to build your own “fake” for the entire subsystem.
You should design fake implementations like this as part of your architecture, and consider them when factoring and refactoring. Often the faking requirements can drive out some real underlying architectural requirements that are not clear otherwise.
Whereas unit tests should test very limited functionality, I think integration tests should be much more like smoke tests and exercise a lot of functionality at once. You aren’t interested in isolating specific behaviour, you want to make it break. If an integration test fails, and no unit tests fail, you have a potential hotspot for adding additional unit tests.
Anway, my example here is a Unit Test. What that means is we only want to test the code inside the single function being tested. We don’t want to actually call any other functions outside the unit under test. Hence mocking: we want to replace all function calls and external objects inside the unit under test with mocks, and then ensure they were called with the expected arguments.
Here is the code I need to test, specifically the ‘fetch’ method of this class:
class CloudImage(object):
__metaclass__ = abc.ABCMeta
blocksize = 81920
def __init__(self, pathname, release, arch):
self.pathname = pathname
self.release = release
self.arch = arch
self.remote_hash = None
self.local_hash = None
@abc.abstractmethod
def remote_image_url(self):
""" Return a complete url of the remote virtual machine image """
def fetch(self):
remote_url = self.remote_image_url()
logger.info("Retrieving {0} to {1}".format(remote_url, self.pathname))
try:
response = urllib2.urlopen(remote_url)
except urllib2.HTTPError:
raise error.FetchFailedException("Unable to fetch {0}".format(remote_url))
local = open(self.pathname, "w")
while True:
data = response.read(self.blocksize)
if not data:
break
local.write(data)
I want to write a test case for the ‘fetch’ method. I have elided everything in the class that is not relevant to this example.
Looking at this function, I want to test that:
- The correct URL is opened
- If an HTTPError is raised, the correct exception is raised
- Open is called with the correct pathname, and is opened for writing
- Read is called successive times, and that everything returned is passed to local.write, until a False value is returned
I need to mock the following:
- self.remote_image_url()
- urllib2.urlopen()
- open()
- response.read()
- local.write()
This is an abstract base class, so we’re going to need a concrete implementation to test. In my test module therefore I have a concrete implementation to use. I’ve implemented the other abstract methods, but they’re not shown.
class MockCloudImage(base.CloudImage):
def remote_image_url(self):
return "remote_image_url"
Because there are other methods on this class I will also be testing, I create an instance of it in setUp as a property of my test case:
class TestCloudImage(unittest2.TestCase): def setUp(self): self.cloud_image = MockCloudImage("pathname", "release", "arch")
Now I can write my test methods.
I’ve mocked self.remote_image_url, now i need to mock urllib2.urlopen() and open(). The other things to mock are things returned from these mocks, so they’ll be automatically mocked.
Here’s the first test:
@mock.patch('urllib2.urlopen')
@mock.patch('__builtin__.open')
def test_fetch(self, m_open, m_urlopen):
m_urlopen().read.side_effect = ["foo", "bar", ""]
self.cloud_image.fetch()
self.assertEqual(m_urlopen.call_args, mock.call('remote_image_url'))
self.assertEqual(m_open.call_args, mock.call('pathname', 'w'))
self.assertEqual(m_open().write.call_args_list, [mock.call('foo'), mock.call('bar')])
The mock.patch decorators replace the specified functions with mock objects within the context of this function, and then unmock them afterwards. The mock objects are passed into your test, in the order in which the decorators are applied (bottom to top).
Now we need to make sure our read calls return something useful. Retrieving any property or method from a mock returns a new mock, and the new returned mock is consistently returned for that method. That means we can write:
m_urlopen().read
To get the read call that will be made inside the function. We can then set its “side_effect” – what it does when called. In this case, we pass it an iterator and it will return each of those values on each call.
Now we call call our fetch method, which will terminate because read eventually returns an empty string.
Now we just need to check each of our methods was called with the appropriate arguments, and hopefully that’s pretty clear how that works from the code above. It’s important to understand the difference between:
m_open.call_args
and
m_open().write.call_args_list
The first is the arguments passed to “open(…)”. The second are the arguments passed to:
local = open(); local.write(...)
Another test method, testing the exception is now very similar:
@mock.patch('urllib2.urlopen')
@mock.patch('__builtin__.open')
def test_fetch_httperror(self, m_open, m_urlopen):
m_urlopen.side_effect = urllib2.HTTPError(*[None] * 5)
self.assertRaises(error.FetchFailedException, self.cloud_image.fetch)
You can see I’ve created an instance of the HTTPError exception class (with dummy arguments), and this is the side_effect of calling urlopen().
Now we can assert our method raises the correct exception.
Hopefully you can see how the mock.patch decorator saved me a spectacular amount of grief.
If you need to, it can be used as a context manager as well, with “with”, giving similar behaviour. This is useful in setUp functions particularly, where you can use the with context manager to create a mocked closure used by the system under test only, and not applied globally.
About us: Isotoma is a bespoke software development company based in York and London specialising in web apps, mobile apps and product design. If you’d like to know more you can review our work or get in touch.