Django framework provides developers with great testing tools and it's dead easy to write tests for views using Django's test client. It has extensive documentation on how to use django.test.Client
to write automated tests. However, we often want to write tests for components that we have no control over when using django.test.Client
. An example of that is Django Middleware which is used to add business logic either before or after view processing. django.test.Client
has no public API for developers to access the internal request
object.
Here is a simple example of a middleware class that creates a stash from data saved in the session.
class Stash(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class StashMiddleware(object):
"""
Reconstructs the stash object from session
and attach it to the request object
"""
def process_request(self, request):
stashed_data = request.session.get('stashed_data', None)
# Instatiate the stash from data in session
if stashed_data is None:
stash = Stash()
else:
stash = Stash(**stashed_data)
# Attach the stash to request object
setattr(request, 'stash', stash)
return None
Let's analyze what needs to be tested:
1. Assert that if the stashed data exists in the session, it should be set as an attribute of the request
2. Assert that if the stashed data doesn't exist in the session, an empty stash is created and attached to the request object
3. Assert that all attributes of the stash can be accessed
How about dependencies? What do we need in order to write this test?
- StashMiddleware class (this can be easily imported)
- request
object as an argument in process_request(). This one is a bit harder to obtain, and since we are writing a unit test, let's just mock it.
We are now ready to write the test
from django.test import TestCase
from mock import Mock
from bugfreeapp.middleware import StashMiddleware, Stash
class StashMiddlewareTest(TestCase):
def setUp(self):
self.middleware = StashMiddleware()
self.request = Mock()
self.request.session = {}
This sets up an instance of StashMiddleware
and mocks a request. I'm using Michael Foord's mock
library to assist me with this. Since we know session
is a dictionary like object, we can mock it with an empty dictionary.
def test_process_request_without_stash(self):
self.assertIsNone(self.middleware.process_request(self.request))
self.assertIsInstance(self.request.stash, Stash)
def test_process_request_with_stash(self):
data = {'foo': 'bar'}
self.request.session = {'stashed_data': data}
self.assertIsNone(self.middleware.process_request(self.request))
self.assertIsInstance(self.request.stash, Stash)
self.assertEqual(self.request.stash.foo, 'bar')
The first test asserts that (without stashed data in the session):
- process_request returns None
- Stash object has been attached to request
The second test asserts that:
- process_request returns None
- Dictionary containing data in session
is unpacked and used to create a Stash object.
- Stash attributes can be accessed
In both cases, we assert for a return value of process_request
. This might sound like a redandunt thing to test for but it actually helps us to identify regressions. Knowing that process_request
returns None, we don't have to worry about this middleware skipping the subsequent middlewares.
Tips
- Not all tests can be written with
django.test.client.Client
. - Keep your dependencies for unit tests as light as possible, use mocks.
- Write unit tests that run fast. Don't test ORM or network calls, try using
mock.patch
instead - Revisit your code if you have a hard time trying to set up dependencies, that normally indicates that the code is too coupled.
Like the sound of how we work? Check out our Careers Page!