Django Unit Testing with Mocks

Here I’m going to talk about how to use mocks when writing unit tests for Django, the Python-based web framework. Using Django test mocks has really opened my eyes on how to write much better unit tests. Previously, and in some cases still do when using 3rd party services, I would use fake API servers to serve fake data for testing end to end. With the mock library, I can easily mock out server responses in Django tests.

 

Integration Vs Unit Tests

At work, our Django web app is using an internal API and it’s using the cache framework. At first when writing unit tests for the code I would run either the internal API server or run my fake Python API server which serves up default HTTP responses. For caching I would just switch to the DummyCacheBackend that comes with Django in my settings file.

However I realized these would be integration tests; tests that have multiple moving parts and cannot test the code in isolation. They weren’t unit tests at all.

Using Mocks

That’s when I started adding mocks and patching functions and classes to return the right values for testing. For Python and Django I’m using the mock library.

Testing Without Mocks

So a typical test would look something like this without Django mocks and hitting the real API:

from django.test import TestCase
from myapp.models import MyModel

class MyModelTestCase(TestCase):
    def test_api_call(self):
        MyModel.do_api_call()
        self.assertEqual(MyModel.api_value, 1)

Django Mock: Adding Mocks To Unit Tests

Then I would add the mocks:

from django.test import TestCase
from mock import patch
from myapp.models import MyModel

class MyModelTestCase(TestCase):
    @patch('internal_api.api_call', return_value=1)
    def test_api_call(self, mock_api_call):
        MyModel.do_api_call()
        self.assertEqual(MyModel.api_value, 1)
        self.assertEqual(mock_api_call.call_count, 1)

Cleaner Re-Usable Mocks

Finally when we have multiple tests that uses the same mocks over and over again, we have to refactor that into the setUp method:

from django.test import TestCase
from mock import patch
from myapp.models import MyModel

class MyModelTestCase(TestCase):
    def setUp(self):
        super(MyModelTestCase, self).setUp()
        patcher_api_call = patch('internal_api.api_call')
        self.mock_api_call = patcher_api_call.start()
        self.addCleanup(patcher_api_call.stop)

    def test_api_call(self):
        self.mock_api_call.return_value = 1
        MyModel.do_api_call()
        self.assertEqual(MyModel.api_value, 1)

Unit tests shouldn’t be treated like lesser code. It should be as clean as possible and don’t ever hesitate when it comes to refactoring the unit test code. It can save you a lot of trouble in the long run. Not only that, documentation is okay to add to your unit tests.

Mocking The Cache

One important note about mocking the Django cache. When you import the cache from django.core.cache, you’re instantiating the cache object at the place it’s being imported.

Let’s say your code looks like this:

# models.py
from django.db import models
from django.core.cache import cache

class MyModel(models.Model):
    field = models.IntegerField()
    def do_something(self):
        cache.set('hello-world', self.field)

That means when you’re patching it for a unit test you’re going to want to do this:

# tests.py
from django.test import TestCase
from mock import patch
from myapp.models import MyModel

class MyModelTestCase(TestCase):
    def setUp(self):
        patcher_cache = patch('myapp.models.cache')
        self.mock_cache = patcher_cache.start()
        self.addCleanup(patcher_cache.stop)

    def test_do_something(self):
        """
        When do_something is called, cache.set is called exactly one
        """
        mymodel = MyModel()
        mymodel.do_something()
        self.assertEqual(self.mock_cache.set.call_count, 1)

How to Deal With Other Legacy Codebases

From Django web framework, I learned the value of mock objects and from there I took that knowledge to AngularJS which has a decent setup for testing (thanks to DI Dependency Injection).

Click here to learn how to break dependencies in your code, using an example in JavaScript and AngularJS. Breaking dependencies and making each class obey the SRP (Single Responsibility Principle) is essential to refactoring legacy codebases.

From what I have seen, most Django web framework projects are well-kept and don’t need as much refactoring as other code bases, however, it is instructive to look at other languages and tutorials on refactoring to see if the ideas can be brought back to Django and Python and applied to them. Django really is the framework for perfectionists with deadlines; somehow most Django refactors end up being straight-forward.

More On Django! Django Books! (October 2017 update)

For more on Django web framework and for a good tutorial to getting started, check out the book Two Scoops of Django: Best Practices. Bought a copy of the Two Scoops of Django book and it’s wonderful, it helped our team of programmers figure out the right way to structure settings.py for production, staging and development environments.

The 2nd edition of Test-Driven Development with Python is going to be released at the end of June 2017 has been released! It features a section on testing with Django and Selenium. Perfect for web developers who want to thoroughly test their Django website.

If you enjoy using the Django web framework, don’t forget to donate to the Django Software Foundation which supports and develops Django further.

References

  1. mock library for Python and documentation for the library
  2. patch methods: start and stop
  3. where to patch (mock library documentation)
  4. TestCase.addCleanup (Python standard library docs for unittest)