Patching - nothing to do with Pirates, but also Scary

Published on: April 20, 2025 | Reading Time: 6 min | Last Modified: April 20, 2025

software development
testing
mock
patching

I always find patching one of the most difficult aspects of writing good code tests, so I wanted to share some tips and tricks on patching and some particular ‘gotchas’ that can be confusing to deal with.

What is patching? Why would I want to do this?

When we write tests, we might call function a which calls some other function b, and we don’t necessarily want to test the correctness of function b or call function b at all. So we change the behaviour of the code such that when function a is executed it doesn’t call function b, it returns a mock object. Furthermore, we might specify what the mock object returns to help our testing. In some circumstances we might be able to achieve this using a mock object, but sometimes this is not possible and we need to somehow be able to change the behaviour of code without being able to pass mock objects into the tested function, and this is what we call patching.

I’ll give some examples below, with code and tests written in python, using the pytest framework to execute tests and with patchers and mocks from the python standard library package unittest. Although these are written for pytest and unittest, the examples can be applied to testing frameworks written in other languages. All the code examples used are available on github.

Mock Example

Let’s say we want to test the write_temperature function.

def write_temperature(service: WeatherService, city):
    temp = service.get_temperature(city)
    return f"The temperature in {city} is {temp}°C"

We can see the function that uses a weather service object passed as an argument and calls the get_temperature method of this object. So, how can we test this? We can create a mock object to represent the service so that we don’t call the method from the real object.

def test_write_temperature_mock():
    mock_ws = Mock()
    mock_ws.get_temperature.return_value = 16.1
    assert write_temperature(mock_ws, "Example city") == f"The temperature in Example city is 16.1°C"

We are using a mock object called mock_ws and setting the return value of its get_temperature method, then making an assertion about the expected return value.

Patching Example

Now let’s say we want to test the get_temperature method. We don’t want to call the real _fetch_weather method, but we have no obvious way to pass a mock object, so this is a good case for patching.

def get_temperature(self, city):
    raw_data = self._fetch_weather(city)
    return raw_data['temperature']

When we run the test, we need to handle the _fetch_weather method, because we don’t want to run the real method which might invoke external API calls and make our test complex. So we could write a test like below.

@patch("weather.example.WeatherService._fetch_weather")
def test_get_temperature(mock_fetch):
    ws = WeatherService()
    mock_fetch.return_value = {'temperature': 16.1}
    assert ws.get_temperature(ws) == 16.1

So, what’s going on here? We are using the @patch decorator to change the behaviour of the patched function at test run time; We are telling the test to return the dictionary {'temperature': 16.1} instead of running the real function.

For patching with Python, the built-in unit test library provides a helpful set of patchers. Personally I prefer to use them as decorators since this keeps the code looking a bit tidier, but you can also use them as context managers (i.e. using a with statement).

Gotchas

As mentioned at the start of this post, I think patching can be tricky to implement correctly. You’re changing the behaviour of code at runtime, and sometimes that’s difficult to reason with. Here are some examples of patching that are more difficult to make sense of.

1. Patching at the Wrong Time

Let’s say we want to patch the choose_base_url method of our WeatherService, to test that the base_url attribute is really being set correctly in the constructor. In this example the constructor is simple, but you can imagine that there might be a more complex examples where a test for this would be very helpful.

class WeatherService:
    def __init__(self, base_url=None):
        self.default_url = "https://example.weather.api"
        self.base_url = self.choose_base_url(base_url)

    def choose_base_url(self, base_url):
        if base_url is None:
            return self.default_url
        return base_url

Let’s say we write a test like this.

def test_choose_base_url():
    service = WeatherService()
    with patch.object(WeatherService, 'choose_base_url', return_value="test-url"):
        assert service.base_url == "test-url"

Let’s talk through what is happening here. We are creating a WeatherService object called service. Then we are patching the WeatherService class and telling the code to return the value test-url at runtime. So it seems like what should happen at runtime is that the mock object is used instead of the real object and our assertion should pass since it checks that the base_url of the mock object is set to the value we specified.

If you try running this test, it will fail, and it might not be obvious why. The source of the issue is that the WeatherService object is instantiated before it is patched, so the patch.object statement actually has no effect. The mock object has its attribute changed, but When we run the final assertion, the service object to being referred to is the real one, not the mock one. If we move the line service = WeatherService() after the patch, we get the intended behaviour and the test will pass.

Another version of this timing issue is changing the mock object after the tested function has been called, like this example.

2. Patching the class, not an Instance

Let’s say we want to test this method.

def write_paris_temperature():
    ws = WeatherService()
    return write_temperature(ws, 'Paris')

We write a test like this

@patch("weather.example.WeatherService", autospec=True)
def test_patch_class_but_real_instance_used(mock_service):
    mock_service.get_temperature.return_value = 18
    assert write_paris_temperature() == "The temperature in Paris is 18°C"

When we run the test, it fails, showing that the actual value returned is a MagicMock. This is confusing because it seems like we have patched the class and set the return value of its get_temperature method. So, what’s going on here? We are patching the weather service class with a mock object called mock_service, so when the class constructor is called, that mock returns another mock, and it’s the get_temperature method of this other mock that is called.

The example below which shows a direct fix for this issue.

@patch("weather.example.WeatherService", autospec=True)
def test_patch_class_mock(mock_service):
    mock_ws = Mock()
    mock_ws.get_temperature.return_value = 18
    mock_service.return_value = mock_ws
    assert write_paris_temperature() == "The temperature in Paris is 18°C"

Now, the weather Service class is still mocked by the mock_service object, but We have created a new mock which will be returned by the class constructor and so the test passes. You might think this seems a bit complicated. I always find when something seems a bit complicated it’s worth considering if it’s sensible. In this case, there is an easier way to deal with this if we have access to the WeatherService class; we can do the same thing more easily with patch.object instead.

@patch.object(WeatherService, 'get_temperature', autospec=True, return_value=18)
def test_patch_class_mock_object():
    assert write_paris_temperature() == "The temperature in Paris is 18°C"

Summary

So, in this post, we have looked at how to replace real code with a mock object in a test. We saw that in some circumstances this is not possible and we need to use patching instead. We saw that this can be tricky to implement correctly and looked at two common issues, firstly, patching at the wrong time, and secondly, patching the class instead of an instance of the class.

That’s all for now. Happy testing!