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.