insomnihack

When sleep eludes, the keyboard beckons

Python Unit Testing with Mock

January 24, 2010 Dale 4 Comments

In the past, while creating unit tests for C# classes, I’ve frequently used a mock object framework such as Moq to isolate classes under test. When I started working in Python, I initially didn’t think I’d need such a framework to write my tests. Creating a stub with a given set of attributes and methods is easy in language like Python.

However, I soon noticed I was writing a lot of these little stubs, and this was ‘dirtying up’ my nice, readable tests. The next problem I encountered was 3rd party libraries with more complex interfaces, like this Twitter library. It contains a client class that dynamically wraps Twitter’s RESTful API. So instead of calling http://twitter.com/statuses/friends.format, you call Twitter.statuses.friends(). The Twitter client doesn’t have a statuses.friends method defined, it creates the URL dynamically from the dot notation you enter.

Creating fakes to test code using such a class would be tedious. Additionally, I was looking at having to pass in instances of other 3rd party classes I was using. The whole thing was getting dangerously close to the DI/IoC mess we’d left behind when we switched from C#. As Jonathon commented a few weeks back:

In Python, hard is a smell.

Enter Mock. Mock is a mocking and testing library for Python by Michael Foord. Unlike many of the other mocking libraries for Python that use the record/replay pattern, Mock uses an action/assert pattern similar to Moq.

Below is a snippet of code that uses the previously mentioned Twitter RESTful client with the dynamic API methods. I’d like to mock up the actual API calls so that I’m not connecting to Twitter when I run my tests.

[python]
from twitter import Twitter, TwitterError

class TwitterClient(object):

def __init__(self, login, password):
self._twitter = Twitter(login, password)

def all_friends(self):
return self._get_statuses(
lambda crs: self._twitter.statuses.friends(cursor = crs))

def all_followers(self):
return self._get_statuses(
lambda crs: self._twitter.statuses.followers(cursor = crs))

def _get_statuses(self, call):
crs = -1
results = []
while crs != 0:
page = call(crs)
users = page[‘users’]
crs = page[‘next_cursor’]
results.extend(users)
return results
[/python]

Mock makes this easy.

[python]
class TwitterClientTests(unittest.TestCase):

def test_all_friends(self):
"""Verify TwitterClient.all_friends()"""
tc = TwitterClient(‘username’, ‘password’)
tc._twitter = Mock()
tc._twitter.statuses.friends.return_value = \
{‘users’:[{‘a’:1}], ‘next_cursor’:0}
result = tc.all_friends()
tc._twitter.statuses.friends.assert_called_with(cursor = -1)
self.assertEquals([{‘a’:1}], result)
[/python]

After creating my test class, I mock out the actual internal Twitter instance that is created. I then set the return value for the API method that all_friends will pass to _get_statuses. I don’t need to reproduce the full JSON returned from a real Twitter API call, I just need enough to test a single loop through the cursor-based call. Once I’m finished, in addition to validating the results returned using regular TestCase asserts, I can verify that my mock method was called with the correct parameters.

This just scratches the surface of what you can do with Mock. It contains facilities for patching, which is useful if you want to mock objects that get created during a method call (i.e. they aren’t easy-to-get-at class members like self.foo). Check out Mock’s documentation and introduction for more details.

Programming Python, unit tests

Comments

  1. jh says

    July 25, 2012 at 8:57 AM

    Would it fail to operate in the same way if you had applied the mock on the TwitterClient class directly rather than instance within it?

  2. Dale says

    September 28, 2012 at 9:42 AM

    Not really. TwitterClient was the class whose interface was being tested. I was verifying its contract.

  3. Lesus says

    August 6, 2013 at 8:28 AM

    Expert master Dale to write more about Python Unit Testing with Mock.

Trackbacks

  1. Confluence: Product Development says:
    July 9, 2012 at 3:46 AM

    Unit testing using mocks…

    Intro In order to simplify the testing of a partic…

Leave a Reply

Your email address will not be published. Required fields are marked *

About Me

Hi, my name's Dale, and this is my tech blog. Read More…

Search

Tags

blogging c# database Django documentation Email furniture git hacks htpc IPython Linux mac nas packaging PyCharm Python Roleplaying tools twitter unit tests virtualenv Visual Studio vmware Windows

Copyright © 2025 · Equilibre Theme on Genesis Framework · WordPress · Log in