kkwiatkowski.dev

Hey, I'm Kamil. Welcome to my private place on the World Wide Web. You can find here entries, mostly about programming, technology, and web development, but sometimes also about life. Make yourself at home.

Patch in Unit Tests - Introduction

Aug. 11, 2023, 7:04 p.m.

Some time ago, during my first job as a developer, I was looking through the tests written by older colleagues - in short, I was looking for good practices and something I could 'base' on.
During this review, I stopped at the definition of the function above which, @patch() was located. I was trying to understand what it was, why someone used it, and how to use it.

Unit testing is an integral part of our development process, and unittest.mock can be our ally in writing more reliable and consistent tests. In unit testing, we focus on testing specific pieces of code. It happens that a fragment of the code uses external dependencies, such as databases, the network, or external services, e.g. downloads data from the website or hits an external API. In such cases, to perform tests in isolation, we need to modify the objects that are used by the code under test. This allows us to simulate different scenarios and test our code in isolation, regardless of actual dependencies.

I would like to invite you for a short introduction to the 'patch' function (from unittest.mock import patch).

To start with, the patch function is used to modify objects to control them in unit tests. To put it simply - we replace an object (e.g. a function, class, attribute) with a binding object and adapt it to our needs.

Let's see an example - this will be a stupid example, but it's just to demonstrate how it works in practice.
We have an example_function that uses the get_value function - let's imagine that in real life get_value performs some complicated calculations and its execution takes a very long time, or inside it has some GET to the API we use and this API has some stability problems, therefore, every time we run all the tests, it slows down the entire process, or for reasons beyond our control, the test crashes because there is a problem with this external API.

Two simple functions in python code

simple small test in python

python3 -m unittest test
.
----------------------------------------------------------------------
Ran 1 test in 10.006s

OK

What can we do? It can now replace such a function and manipulate its operation to suit our needs.
What's more, he can do it in 3 ways!
I will show the same example in which, for the duration of the test, we will replace the get_value function in such a way that it simply returns 10 and this example will be shown in 3 forms.

1. Context manager

 

python code with patch function used with context manager

it's important to note that here in the patch("main.get_value") function I gave as a parameter - where the function was called. The argument is the name of the object to be replaced and should be a string representing the import path to the object. A common mistake is to include the place of the function definition here. The get_value definition could just as easily have been in another file, and I could have imported it in main.py and the path in the patch function would not have changed.

2. Decorator

python code with test using patch as decorator

In this case, we use the @patch() decorator, whereas a parameter we again provide the path to the object we are replacing, and in the test declaration (test_example_function_decorator) we add a parameter (we can name it whatever we want) that will represent the mocked object.

3. Manually

python code with test using patch as decorator

And the manual version, I don't think I've ever seen it used this way, but I'm showing that it's possible. Our object will be replaced between p.start() and p.stop(), as you can probably guess.

Each of these tests, of course, also improved our time.

python3 -m unittest test
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

 

As you can see in the examples, I manipulated the return value of the get_value function by using the .return_value method and assigning it the value 10. This meant that in the place "main.get_value" we indicated, when this function is called, the value 10 will be returned.


That's it for today, I just wanted to make a little introduction. In general, the unittest module is large, the unittest.mock module contained in it is also large, and patch is just one function that is in this module. There may be other entries related to unittest.mock in the future, the topic is quite interesting.