Mocking My Way Through Python Tests

6 minute read

Mocking is a powerful tool that I like to turn to while unit testing. Python ships with a flexible mocking package in the unittest library called unittest.mock. Mocks can help simplify test setup, allow you to test code that depends on features not yet implemented, and minimize undesired side effects in our test suite. I would like to show you how I use mocks in Python Django. We will cover the main reasons you would consider mocks in your unit tests, different styles of writing mocks, and common mocking anti-patterns.

Why Mock Test Dependencies

Mocks allow us to get to the point of our tests. Some parts of our codebase might be tricky to setup without introducing a lot of dependent objects. It also makes sense to mock services that we are not unit testing. Some services might have undesired consequences in a test suite. This is common when interacting with 3rd party services such as payment gateways and social media applications. In most cases we would prefer our test suite to be resilient to 3rd party service outages or dependencies. This makes tests run faster and easier to deploy to CI environments.

I also like to use mocks when practicing TDD. In these cases my mocks might even be temporary stubs that I use while the dependent service is in development. This can be extremely useful when working with other developers on the same feature. Usually you already have a good idea of what a response or input for a required service is. In those cases you can quickly mock that service and get on with writing unit tests for code that would otherwise be blocked.

Various Flavors of Mocks

Unittest mocks come in a variety of flavors. It can be a little daunting when visiting the documentation. Let’s take a look at the 4 ways I like to write mocks. For each example we will be looking at implementing a feature in a Django blog app that will allow us to post status update about a blog article to Twitter. Our code that we will be testing looks like this:

class TwitterClient(object):
    def __init__(self):
        self.client = twitter.Api()

    def post_update(self, message):
        self.client.PostUpdate(message)

class Post(models.Model):
    ....

    def post_to_twitter(self, client):
        resp = client.post_update('Check out my new article: {}'.format(self.title))
        if resp == 200:
            return 'Posted {}'.format(self.title)
        return 'Unsuccessful. Try again...'

1. Mock Class

hen I first approached writing mocks in Python I went for what I thought was the most obvious pattern. Creating instances of Mocks with the generic Mock class is straightforward. In this example we create an instance of Mock and assign it to twitter_api. We can pass twitter_api to Post’s post_to_twitter method and assert that the post_update method is called with a notification about about the Post’s title.

from mock import Mock

class PostTests(TestCase):
    def setUp(self):
        self.post = factories.PostFactory()

    def test_post_to_twitter(self):
        twitter_api = Mock()

        self.post.post_to_twitter(client=twitter_api)
        twitter_api.post_update.assert_called_with(
            'Check out my new article: {}'.format(self.post.title)
        )

In theory this looks good but there is a very dangerous pitfall lurking here. Mock is a little too good at faking its role. The post_update method does not even need to exist on the instance of the TwitterClient class that we created for this test to pass.

For this reason, I only like to use Mock in cases where I need a true test double. Usually this is early in the process of developing a new feature where the service I need is not yet available.

2. Create Autospec

We can get a little better if we use mock.create_autospec method. This ensures that we can only call methods that are part of the Class API specification. In this example we a mock of our TwitterClient class and then assign an instance of that class to client. The rest of the test is pretty similar as before. However, this time the test will fail if TwitterClient does not respond to post_update.

from mock import create_autospec
from mysite.blog.models import TwitterClient

class PostTests(TestCase):
    def setUp(self):
        self.post = factories.PostFactory()

    def test_post_to_twitter(self):
        MockTwitterClient = create_autospec(TwitterClient, spec_set=True)
        client = MockTwitterClient()


        self.post.post_to_twitter(client)
        client.post_update.assert_called_with(
            'Check out my new article: {}'.format(self.post.title)
        )

3. Mock Patch

The method I turn to most often when writing mocks is mock.patch. This is a Python decorator method that can be added to each test. In this example (without even importing TwitterClient) we create a mock_twitter_client by attaching mock.patch with the full module path of TwitterClient. We can then create an instance of the mock twitter client and test as before. At this stage it might also make sense to write an assertion on the return value.

import mock

class PostTests(TestCase):
    def setUp(self):
        self.post = factories.PostFactory()

    @mock.patch('mysite.blog.models.TwitterClient')
    def test_post_to_twitter(self, mock_twitter_client):
        client = mock_twitter_client()

        client.return_value = 200

        reponse = self.post.post_to_twitter(client)
        client.post_update.assert_called_with(
            'Check out my new article: {}'.format(self.post.title)
        )

        self.assertEqual('Posted {}'.format(self.post.title), response)

4. Mock Patch Object

This example is almost identical to mock.patch. However, by calling object on patch we can also include the method and the return value that we are mocking. One thing to keep in mind here is that we still need to import TwitterClient into the test. With a little cut and paste it’s easy to also test what happens when the response code is something other than 200.

import mock
from mysite.blog.models import TwitterClient

class PostTests(TestCase):
    def setUp(self):
        self.post = factories.PostFactory()

    @mock.patch.object(TwitterClient, 'post_update', return_value=200)
    def test_post_to_twitter(self, mock_post_update):
        client = TwitterClient()

        response = self.post.post_to_twitter(client)
        mock_post_update.assert_called_with(
            'Check out my new article: {}'.format(self.post.title)
        )

        self.assertEqual('Posted {}'.format(self.post.title), response)

    @mock.patch.object(TwitterClient, 'post_update', return_value=400)
    def test_post_to_twitter(self, mock_post_update):
        client = TwitterClient()

        response = self.post.post_to_twitter(client)
        mock_post_update.assert_called_with(
            'Check out my new article: {}'.format(self.post.title)
        )

        self.assertEqual('Unsuccessful. Try again...', response)

Mocking Anti-patterns

Mocks are a useful tool to have while unit testing. However, like most things they are not bullet proof.

When testing with mocks it’s important that you test the code that you are trying to write tests for. It seems obvious but it’s entirely possible to write a test with so many mocks that the only thing left are some meaningless assertions about code that doesn’t exist in your project.

Mocks are not substitutes for factories. When possible always use real data dependencies. Mocks really should be a last resort for times when it would be tricky to create the specific state or dependent objects that you need to write a test.

Mocks are useful for testing 3rd party services like we did throughout the examples. However, there are tools that can make testing 3rd party libraries even better. One tool I would like to explore more is VCR. The VCR library allows you to record 3rd party service responses as ‘cassettes’. From there you can interact with the service pretty much as you would in production. For each request you will received a the cached recorded response captured in your cassettes. If the API ever updates, it’s just as easy to rerecord a ‘cassette’.

I prefer not to use Mocks in my integration tests. The whole point of these types of tests is to test your entire stack and mocking any part of the stack would not be a true integration test. The only exception to this would be any interaction with a 3rd party service.

Conclusion

Mocking is a great tool to use in situations where supporting dependencies in a test have either undesired side effects that should be faked or when it is complicated to setup supporting dependencies in a test. Even so the need for mocking can sometimes be an indicator that the current implementation is too complex. Also, it is entirely possible to write mocks that can mislead your tests. Mocks are best used sparingly but can be a powerful tool when practicing TDD.

Updated: