How to use functions as Mocks in Python tests
One common question about testing is how to avoid calling “heavy services” during test execution.
If there is a need to call services like Redis, Docker, MySQL, RabbitMQ, or any other service for every test, it might be necessary to install them on the test server. However, this approach consumes a lot of resources and increases the execution time, which is not ideal for developers.
Another important consideration is to avoid calling real URLs. Since we have no control over those addresses, they might not be available at all times.
To address these issues, we can make use of Python Mocks. With mocks, we can verify that our code makes a call to an external URL and simulate edge cases, such as the URL not being available or returning a successful response.
In this example project, I will use Poetry as the package manager, the requests
package to make calls to an external URL, and pytest
with unittest
as testing utilities.
The following code has been tested in Python 3.11
Using Poetry to setup the project
Poetry is a package manager easy to use. You can download it from https://python-poetry.org/ . Here is the list of dependencies to use.
We have black
, a code formatter, pytest
, and pytest-mock
to provide a simple way to create mocks in Python. These will be included as development dependencies. We do not need to deploy them to our production code.
For our production dependencies, we are going to use Python 3.11
and requests
, a library for making HTTP calls to any URL.
Creating a Python script to call an API
Like any Python script, we are doing something simple. I will proceed to make three of the most common API calls: a GET call, a POST call, and another POST call with data.
If I execute this script, it will work as long as the JsonPlaceholder URL is available.
Using mocks to override the function behaviour
Let’s frequently use the patch
method from unittest.mock
. With this decorator, we can patch the desired method and turn it into a mock
. In the process, it will provide us with many utility functions that allow us to check if the method has been called, which parameters were passed, and even return a fake value as desired.
In this case, the function we want to mock is requests.get
. Make sure you import get
from requests
and include the complete package path, including the package name.
The functions within the mocks are so diverse that sometimes you can choose between using assert
or using the internal assertions within the mocks.
Check if a function was called once, using the decorator approach
If we want to use assert
from Python. The mock comes with a function that returns true or false, named called_once
.
See how we use a decorator, above our test test_call_single_endpoint_mock
. Using this mode, we avoid code indentation.
Check if the function was called once, using the with keyword
We can also choose not to use the decorator. In this case, we don’t need a patch. Instead, we simply add an extra line with indentation. Personally, I prefer the decorator version, but it never hurts to learn the alternative approach.
Check if the function was called, using the mocker parameter
You can use mocker
as a parameter to create a mock of the function that you want to override. This method also helps to avoid adding another level of indentation. The advantage of using this parameter is that you can perform multiple patching of mocks within the same method, without accumulating decorators.
Check if the function was called with specific parameters
We have two versions of these assertions. We can check if a function was called with specific parameters using the mock’s function called assert_called_with
. There is another variation of this assertion called assert_called_once_with
which ensures that the function was called only once.
Grouping tests
If we want to have better organization for our tests, we need to group them inside a class. You can even combine grouped tests and non-grouped tests within the same file.
For the following example, we will tell the mock object what to return. This can be useful if you want to force a specific flow to check how your functions behave when they are mocked.
Remember that in a class, the first parameter of every function will always be self
. So, the second parameter will become your function as a mock.
Check consecutive calls from a method
Inside of the mocked function, there is a method called side_effect
. This is useful when you want to test consecutive method calls and test its returns.
Also, the side_effect
method is used to send Exceptions as response.
Check that a call is equal to, using call
If we prefer not to use assert_called_with
or assert_called_once_with
, we can instead utilize the call
function provided by unittest
. This function returns a call to Mock
or MagicMock
, allowing us to test if the calls at specific points in time match what the mock has recorded.
Check the n parameter of a function was a specific value
This is a variation of a test where we want to know if a parameter in a specific place of a function was equals to a certain value. Since we have the parameters as a list, we can access to its index.
In this example we can obtain all parameters of a mock call inside of call_args
.
Final result
This is the final code containing all the tests that I have made. It doesn’t cover 100% of all application cases, but I hope those are enough to show and describe.
The link to the repository will be in the Recommendations section.
Recommendations
You can see the repository in this link .
For more information about unittest.mock
check its documentation .