pytest: How to mock in Python
If you have trouble understanding mocks for testing in Python like me, then this post is for you.
To follow the post, please install
Both can be installed via
The code in this post can be found in
What is mock?
Here’s an example. Imagine that you have a function called
compute(). Part of its code contains an
expensive_api_call() that takes 1,000 seconds to run
import time def compute(x): response = expensive_api_call() return response + x def expensive_api_call(): time.sleep(1000) # takes 1,000 seconds return 123
I would expect that
124, so I would write a test in Python:
def test_compute(): expected = 124 actual = compute(1) assert expected == actual
Because of the API call, this test also takes 1,000 seconds to run. This is too slow for a simple test.
When I write this test, I don’t really care whether the API call runs or not. What I want to know when I develop is that my code works as expected when API returns correct data.
If I can provide fake data without calling the API, then I don’t have to sit there are wait for the test to complete. This is where mocks come in.
Test an interface if possible
Shorten the feedback loop
In other words, it is a trick to shorten development feedback loop.
Let’s review again: I have two options of writing a test for
- Write a single test on
compute()that contains both the api call
expensive_api_call()and the computation
result + x. Takes 1,000 seconds to run.
- Write two tests: mock the API call in the test for
compute(), and write another test to test that the API call returns correct data. The first test will be instant, and the second test will take 1,000 seconds.
Option 2 is better because the developer can choose run only the fast tests when she is developing. She can now run the integration tests elsewhere, for example, on a CI/CD server as part of the build process, that does not interfere with her flow.
So how do I replace the expensive API call in Python?
Mocking in pytest
In Python, the solution is a library called
The definition of
mock in Merriam-Webster
to imitate (someone or something) closely : MIMIC e.g. a mockingbird was mocking a cardinal
In Python, you use mocks to replace objects for testing purposes. In the next section, I am going to show you how to mock in
The workhorse: MagicMock
The most important object in
mock is the
MagicMock object. Playing with it and understanding it will allow you to do whatever you want.
The basic idea is that
MagicMock a placeholder object with placeholder attributes that can be passed into any function.
- mock a constant,
- mock an object with attributes,
- or mock a function, because a function is an object in Python and the attribute in this case is its return value.
Let’s go through each one of them.
Recipes for using mocks in pytest
We will use pytest-mock to create the mock objects.
mocker fixture is the interface in
pytest-mock that gives us
Before diving in: what confused me
Before I go into the recipes, I want to tell you about the thing that confused me the most about Python mocks: where do I apply the mocks?
In general, when you mock an object, you want to mock where the object is imported into not where the object is imported from.
This caused so many lost time on me so let me say it again: mock where the object is imported into not where the object is imported from.
For more details, see the offical docs on this topic.
I will also demonstrate this point in the recipes.
The code used in this post can be found in
1. Mocking a constant
double() reads a constant from another file and doubles it.
# functions.py from .constants import CONSTANT_A # CONSTANT_A = 1 def double(): return CONSTANT_A * 2
CONSTANT_A=1, each call to
double() is expected to return
CONSTANT_A in tests, I can use
patch.object() and replace the
CONSTANT_A object with another constant.
import mock_examples.functions from mock_examples.functions import double # note that I'm mocking the module when it is imported, not where CONSTANT_A is from def test_mocking_constant_a(mocker): mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2) expected = 4 actual = double() # now it returns 4, not 2 assert expected == actual
2. Mocking a function
main.py, I have a slow function
from mock_examples.slow import api_call def slow_function(): api_result = api_call() # do some more stuff here return api_result
where it is slow because in
def api_call(): time.sleep(3) return 9
So each test will take at least 3 seconds to run.
When I mock a function, what I really care about is its return value, so I can patch the function with
def test_slow_function_mocked_api_call(mocker): mocker.patch( # api_call is from slow.py but imported to main.py 'mock_examples.main.api_call', return_value=5 ) expected = 5 actual = slow_function() assert expected == actual
This removes the dependency of the test on an external API or database call and makes the test instantaneous.
3. Mocking a class
For classes, there are many more things that you can do. Remembering that
MagicMock can imitate anything with its attributes is a good place to reason about it.
I will only show a simple example here. For more complex ones, I recommend reading the references in the next section.
I have a class
Dataset that has a slow method,
# slow.py class Dataset: def __init__(self): self.data = None def load_data(self): time.sleep(4) self.data = 'slow data'
It is called as part of the
# main.py def slow_dataset(): dataset = Dataset() return dataset.load_data()
For the test example, I am using
patch.object to replace the method with a tiny function that returns the data that I want to use for testing:
from mock_examples.main import slow_dataset def test_mocking_class_method(mocker): expected = 'xyz' def mock_load(self): return 'xyz' mocker.patch( # Dataset is in slow.py, but imported to main.py 'mock_examples.main.Dataset.load_data', mock_load ) actual = slow_dataset() assert expected == actual
Useful reference for mocking a class
There are many scenarios about mocking classes and here are some good references that I found:
Should I replace every API call with mocks?
No. I would combine integration tests and unit tests but not replace.
For developers, unit tests boost productivity. But for product development, integration tests are absolutely necessary. I still want to know when APIs external to the project start sending data that breaks my code. The testing can happen outside of developer’s machine, however.
When should I mock?
In my opinion, the best time to mock is when you find yourself refactoring code or debugging part of code that runs slow but has zero test.
Trying to make changes without a test means you are incurring technical debt for the future and making teammates pay for it.
In this case, if my goal is making changes to the computations, I would figure out how to mock the data connectors and start writing tests.
The tests seem to be tied to how I implement the code. Isn’t that a bad thing?
Mocks are always white-box tests. You can’t use them without peeking into the code, so they are most useful for developers and not so much for testing specifications. It is a tradeoff that the developer has to accept.
Can you replace the return value in the same test twice??
Answer: yes. I don’t know how to do this with the Python base library
mock but it can be done with
def test_mocking_constant_twice_in_same_test(mocker): mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 3) expected_1 = 6 actual_1 = double() mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 10) expected_2 = 20 actual_2 = double() assert expected_1 == actual_1 assert expected_2 == actual_2
Learn form my mistakes
The most common mistake that I make when I write tests with mocks is… that I mock after I make the method call I want to patch:
actual = compute(x) mocker.patch('some.function', fake_function) # And I wonder why compute() wasn't patched :(
More than once I spent more than 15 minutes trying to figure out what was wrong 🤦♂️. If you are having trouble getting mocks to work,
- Make sure you are mocking where it is imported into
- Make sure the mocks happen before the method call, not after