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.
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
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
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
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.