When building an application it's hard not to depend on some outside resource, that you have no control over. When writing unit tests for this type of application it gets tricky, but thanks to mocking you are able to do so. Mocking comes in two forms, manual (a.k.a monkey patching) or using a mocking framework, we will be using the latter. A mocking framework creates objects that add expectations, define their methods, and return values for each method call in a simple way.
There are a few mocking frameworks available for python:
- pymox - http://code.google.com/p/pymox/
- pymock - http://theblobshop.com/pymock/
- mock - http://www.voidspace.org.uk/python/mock/index.html
- dingus - http://pypi.python.org/pypi/dingus/
- pmock - http://pmock.sourceforge.net/
- mocker - http://labix.org/mocker
There is no shortage of python mocking frameworks. In this example we will be using the pymox framework, also known as mox. You can easily install mox using easy_install (easy_install mox) or pip (pip install mox). Ubuntu has a package for it (python-mox).
Mox uses the record/replay model, meaning you first start off in record mode by creating a mock object. Calling methods which intern set expectations on the mock, once done you set the framework into replay mode. When the framework is in replay mode, you run your tests over the mock objects. At this point the framework will verify that your code is calling all the correct methods on the mocks.
Moxs has its own DSL (Domain Specific Language) for defining mocks. Below we will demonstrate an example of using moxs to mock out the twitter api in a Django view. This will allow us to write unit tests for the view.
We will start off with the view that uses the twitter api:
from django.conf import settings
from django.shortcuts import render_to_response
from django.template.context import RequestContext
import twitter
import urllib2
def list(request):
updates = []
try:
api = twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD)
updates = api.GetUserTimeline()
except urllib2.URLError, e:
pass
return render_to_response('django_twitter/list.html',
{'twitter_updates' : updates },
RequestContext(request)
)
The above snippet is a simple Django view. We initiate an empty list for storing updates, then we wrap all the twitter.Api interactions in a try except block catching any URLErrors. Lastly, we return using a Django shortcut method render_to_response passing the template context and a RequestContext. If no exception is raised the template will get a list of twitter updates. If there is a exception then the template will get a empty list. This a simple view, but because of its dependency on the twitter api you couldn't easily write unit tests to check the functionality of this view.
def test_list(self):
res = self.client.get('/list/')
self.assertEquals(res.status_code, 200)
self.assertTemplateUsed(res, 'django_twitter/list.html')
self.assertTrue(res.context.has_key('twitter_updates'))
self.assertEquals(res.context['twitter_updates'], [])
If we had a test case like the one above, it would pass if there were no updates posted to twiter. As soon as twitter is updated this test will fail. This being because twitter_updates will no longer be an empty list, it would contain the newly posted status.
Next is an example of using moxs to fix this problem.
# Complete Testcase using mox:
from django.test import TestCase
from django.conf import settings
import twitter
import mox
import urllib2
class ListViewTestCase(TestCase):
urls = 'django_twitter.urls'
def setUp(self):
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
def test_list(self):
# Create a status
status = twitter.Status()
status.text = 'hello world'
# Setup mock for twiter api
api = self.mox.CreateMock(twitter.Api)
api.GetUserTimeline().AndReturn([status])
self.mox.StubOutWithMock(twitter, 'Api', use_mock_anything=True)
twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD).AndReturn(api)
self.mox.ReplayAll()
res = self.client.get('/list/')
self.assertEquals(res.status_code, 200)
self.assertTemplateUsed(res, 'django_twitter/list.html')
self.assertTrue(res.context.has_key('twitter_updates'))
self.assertEquals(res.context['twitter_updates'][0].text, 'hello world')
self.mox.VerifyAll()
def test_list_url_error(self):
# Setup mock for twiter api
self.mox.StubOutWithMock(twitter, 'Api', use_mock_anything=True)
twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD).AndRaise(urllib2.URLError((500, 'Error')))
self.mox.ReplayAll()
res = self.client.get('/list/')
self.assertEquals(res.status_code, 200)
self.assertTemplateUsed(res, 'django_twitter/list.html')
self.assertTrue(res.context.has_key('twitter_updates'))
self.assertEquals(res.context['twitter_updates'], [])
self.mox.VerifyAll()
Don't worry if some of this does not make sense yet. Lets start from the top, we import all of our required modules, and since we're using Django for this application we use it's test case as the base for our test case class.
def setUp(self):
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
For the setUp we're initialising a mox object to use for mocking and stubbing in this test case. In the tearDown we call UnsetStubs this will be more clear later when we review what stubbing means. Simply this method is setting stubbed out methods and modules back to the original state before any stubbing took place.
def test_list(self):
# Create a status
status = twitter.Status()
status.text = 'hello world'
# Setup mock for twiter api
api = self.mox.CreateMock(twitter.Api)
api.GetUserTimeline().AndReturn([status])
self.mox.StubOutWithMock(twitter, 'Api', use_mock_anything=True)
twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD).AndReturn(api)
self.mox.ReplayAll()
res = self.client.get('/list/')
self.assertEquals(res.status_code, 200)
self.assertTemplateUsed(res, 'django_twitter/list.html')
self.assertTrue(res.context.has_key('twitter_updates'))
self.assertEquals(res.context['twitter_updates'][0].text, 'hello world')
self.mox.VerifyAll()
We start off by creating a mock api (twitter.Api) object. This mock will enforce only the methods that truly exist on the original object that can be called. If you were to call api.ShowMeTheTwitterFailWhale() you would get a exception similar to "Method called is not a member of the object". This is a nice feature if you need to enforce the interface of an object, but this can not be used in all cases since python is a dynamic language, methods and attributes can be added to a class at runtime. For scenarios like this moxs comes with and additional method that creates a generic mock object mox.CreateMockAnything(). Now that we have a mock Api object we want to mock out the method call GetuserTimeLine we do this in the next line:
api.GetUserTimeline().AndReturn([status])
This says when you call api.GetUserTimeLine return a list with one status update, pretty straight forward. I had noted that mox also allows you to stub out an object within a module.
self.mox.StubOutWithMock(twitter, 'Api', use_mock_anything=True)
twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD).AndReturn(api)
self.mox.ReplayAll()
The first line calls the StubOutWithMock method and passes in the twitter module, and the string of the object to stub out, and setting the use_mock_anything to True. This will replace the Api object in the twitter module with a MockAnything object (will not enforce the interface). Since this is a mock object we can specify our own return for a call to twitter.Api(). Moxs also allows you to set expectations about args that need to be passed into a function. When twitter.Api is called with the following agrs then it will return our mock api object that was created earlier. We are using use_mock_anything=True for the stubbing since the object that will be returned will be enforcing the original interface. The last line here tells the framework to set itself to replay mode. All the calls above were in record mode.
res = self.client.get('/list/')
self.assertEquals(res.status_code, 200)
self.assertTemplateUsed(res, 'django_twitter/list.html')
self.assertTrue(res.context.has_key('twitter_updates'))
self.assertEquals(res.context['twitter_updates'][0].text, 'hello world')
self.mox.VerifyAll()
First we assert that the status_code is equal to 200 (meaning no errors when rendering the page), then we make sure the correct template was used. We check to make sure the context passed to the template, had a twitter_updates key. Lastly the value is a list with the first element of the list that has a property of text equal to 'hello world'. The last line tells the framework to make sure that all the expecations were met.
The above test will test when twitter is working perfectly (which is not always the case). Moxs allows us to test our exception handling code easily.
self.mox.StubOutWithMock(twitter, 'Api', use_mock_anything=True)
twitter.Api(username=settings.TWITTER_USERNAME,
password=settings.TWITTER_PASSWORD).AndRaise(urllib2.URLError((500, 'Error')))
self.mox.ReplayAll()
Notice that we are no longer creating an Api mock object to return to the view. Instead we are directly going to the stubbing out process, this is because we will not need to return anything to the view, since we are going to raise an exception. The use of AndReturn allowed us to specify a return, there is also a AndRaise method. Instead of returning a value, it will raise an exception. The above example raises a URLError exception, doing so we are able to run through our exception handling code and be sure our view will still function even if twitter is down.
Moxs provides many other tools to help with mocking and stubbing your objects to better test your code. I would also recommend reviewing the wiki pages on the moxs homepage for a good quick run down of how to use the other features of the framework.
