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!