How To Mock In Pytest (A Comprehensive Guide)
Calling external systems in your unit tests can be slow, unreliable, and expensive.
The answer lies in Mocking.
Mocking is a technique that allows you to isolate a piece of code being tested from its dependencies so that the test can focus on the code under test in isolation.
What Is Mocking?
Mocking is a technique allows you to isolate a piece of code being tested from its dependencies so that the test can focus on the code under test in isolation.
This is achieved by replacing the dependencies with mock objects that simulate the behaviour of the real objects.
mocker
Fixture
The pytest-mock
plugin provides a mocker
fixture that can be used to create mock objects and patch functions.
The mocker
fixture is an instance of the MockFixture
class, which is a subclass of the unittest.mock
module.
It offers the following methods:
-
mocker.patch
- Patch a function or method -
mocker.patch.object
- Patch a method of an object -
mocker.patch.multiple
- Patch multiple functions or methods -
mocker.patch.dict
- Patch a dictionary -
mocker.patch.stopall
- Stop all patches -
mocker.patch.stop
- Stop a specific patch
We can mock the PI
constant to test the function with a different value of PI
in the mock_examples/area.py
file.
tests/test_area.py
from mock_examples.area import area_of_circle
def test_area_of_circle():
"""
Function to test area of circle
"""
assert area_of_circle(5) == 78.53975
Mock A Function: Create or Remove file
Let’s consider a file handler with 2 functions - create_file
and remove_file
that create and remove a file respectively.
def test_create_file_with_mock(mocker):
"""
Function to test make file with mock
"""
filename = "delete_me.txt"
# Mock the 'open' function call to return a file object.
mock_file = mocker.mock_open()
mocker.patch("builtins.open", mock_file)
# Call the function that creates the file.
create_file(filename)
# Assert that the 'open' function was called with the expected arguments.
mock_file.assert_called_once_with(filename, "w")
# Assert that the file was written to with the expected text.
mock_file().write.assert_called_once_with("hello")
def test_remove_file_with_mock(mocker):
"""
Test the removal of a file using mocking to avoid actual file system operations.
"""
filename = "delete_me.txt"
# Mock os.remove to test file deletion without deleting anything
mock_remove = mocker.patch("os.remove")
# Mock os.path.isfile to control its return value
mocker.patch("os.path.isfile", return_value=False)
# Mock open for the create_file function
mocker.patch("builtins.open", mocker.mock_open())
# Simulate file creation and removal
create_file(filename)
remove_file(filename)
# Assert that os.remove was called correctly
mock_remove.assert_called_once_with(filename)
# Assert that os.path.isfile returns False, simulating that the file does not exist
assert not os.path.isfile(filename)
Mock A Function — Sleep
Often your code may contain sleep
functionality, for e.g. while waiting for another task to execute or get data.
import time
from mock_examples.sleep_function import sleep_for_a_bit
def test_sleep_for_a_bit_with_mock(mocker):
"""
Function to test sleep for a bit with mock
"""
mocker.patch("mock_examples.sleep_function.time.sleep")
sleep_for_a_bit(duration=5)
time.sleep.assert_called_once_with(
5
) # check that time.sleep was called with the correct argument
Mock External Rest API Calls
Probably one of the most interesting and useful cases is learning to mock external APIs.
mock_examples/api.py
from typing import Dict
import requests
def get_weather(city: str) -> Dict:
"""
Function to get weather
:return: Response from the API
"""
response = requests.get(f"https://goweather.herokuapp.com/weather/{city}")
return response.json()
tests/test_api.py
from mock_examples.api import get_weather
def test_get_weather():
"""
Function to test get weather
"""
response = get_weather(city="London")
assert type(response) is dict
tests/test_api.py
def test_get_weather_mocked(mocker):
mock_data = {
"temperature": "+7 °C",
"wind": "13 km/h",
"description": "Partly cloudy",
"forecast": [
{"day": "1", "temperature": "+10 °C", "wind": "13 km/h"},
{"day": "2", "temperature": "+6 °C", "wind": "26 km/h"},
{"day": "3", "temperature": "+15 °C", "wind": "21 km/h"},
],
}
# Create a mock response object with a .json() method that returns the mock data
mock_response = mocker.MagicMock()
mock_response.json.return_value = mock_data
# Patch 'requests.get' to return the mock response
mocker.patch("requests.get", return_value=mock_response)
# Call the function
result = get_weather(city="London")
# Assertions to check if the returned data is as expected
assert result == mock_data
assert type(result) is dict
assert result["temperature"] == "+7 °C"
When Should You Mock?
When you want to test the functionality as a system you need to test the real connections e.g. your API can connect to the database or external API.
However, when testing within the unit, for e.g. object transformation, or logic, it’s advisable to mock external resources that are not part of the module.