Patching on setUp in Python's unittest

Patching on setUp in Python’s unittest

One of my favorite programming languages is Python, and I think that tests should be a foundational part of software development, so it is not strange that I delve a lot in Python’s unittest.

In this post we are going to show how to use unittest.mock.patch not in a decorator (the usual case) but in the setUp method.

Why should you care about this?

Well, I have not cared about this after more than a decade of working with python (intermittently), because I was just used to the patch decorator. However, we can stop repeating the code and save some precious space (and possible bugs) if we just re-use the code by injecting some code in the setUp method.

But I use pytest so I am not interested in this

I would rather have the minimal dependencies in my projects and not depend on any package that is not needed 100%.

unittest comes from the standard library of Python so I prefer to use it unless there is a hard requirement to use pytest.

However, to each its own, if you are happy with pytest that is perfect and I am glad you feel that way.

What is this setUp method?

There are two methods for preparing the environment for the test in unittest:

  • setUp runs before each test.
  • setUpClass runs once before all tests.

In our case we are going to assign to the implicit object (self) some mocks to have it ready for each test method.

Analogously there are two methods for cleaning up the resources we have initialized:

  • tearDown runs after each test.
  • tearDownClass runs once after all tests.

By the way, notice that the methods are camelCase, hence not following the snake_case convention.

How can I patch methods/classes in setup?

import unittest


class TestCase(unittest.TestCase):
    def setUp(self):
        my_patcher = patch('module.path.object')
        self.mock_func = my_patcher.start()
        self.addCleanup(my_patcher.stop)

    def test(self):
        self.mock_func.return_value = 2

        function_that_calls_to_module_path_object()

        self.assertEqual([call()], self.mock_func.call_args_list)

An example: patching GET requests

Let suppose we are building a dictionary wrapper and we are going to query the dictionaryapi.dev API.

We first create a function to get word definitions:

# dictionary/definition.py
from typing import Any

import requests


def get_word_definition(word: str) -> list[dict[str, Any]]:
    try:
        response = requests.get(f'https://api.dictionaryapi.dev/api/v2/entries/en/{word}')
        return response.json()
    except RequestException as exception:
        raise DictionaryException(
            f'Word meaning for {word} could not be obtained. Exception: {exception.__class__.__name__}'
        ) from exception

But we want to test that we call to the get method of requests and that, in case of an exception, we are prepared:

# dictionary/tests/test_definition.py
import unittest
from unittest.mock import MagicMock, patch

from requests.exceptions import Timeout

from dictionary.definition import get_word_definition, DictionaryException


class TestDefinition(unittest.TestCase):
    def setUp(self):
        requests_patcher = patch('dictionary.definition.requests')
        self.mock_requests = requests_patcher.start()
        self.addCleanup(mock_requests.stop)

    def test_get_word_definition_success(self):
        expected_response = [
            {
                'word': 'house'
            }
        ]

        word = 'house'

        mock_response = MagicMock()
        mock_response.json.return_value = expected_response
        self.mock_requests.get.return_value = mock_response

        response = get_word_definition(word)

        self.assertEqual(expected_response, response)
        self.assertEqual([call(word)], self.mock_requests.get.call_args_list)
        self.assertEqual([call()], mock_response.json.call_args_list)

    def test_get_word_definition_timeout(self):
        word = 'house'

        self.mock_requests.get.side_effect = Timeout()

        with self.assertRaises(DictionaryException) as context:
            get_word_definition(word)

        self.assertEqual(
            f'Word meaning for {word} could not be obtained. Exception: Timeout',
            str(context.exception)
        )
        self.assertEqual([call(word)], self.mock_requests.get.call_args_list)

As you see above, we have a self.mock_requests instance attribute that contains a mock to the requests library. In this case you could argue that the gains are not much, but imagine if the get_word_definition would have 3 dependencies that need to be tested, so you could save a lot of repeated code by using this technique.

Conclusion

We have shown how to patch in the setUp method, this is useful for sharing mocks between several tests, hence keeping the minimal code in each one of the test methods.