unittest.mock is a standard library that lets you replace parts of your system under test with mock objects, so you can test them in isolation. pytest-mock is a pytest plugin that provides a convenient fixture for using unittest.mock within pytest tests.

Let’s see pytest-mock in action. Imagine you have a function that fetches data from an external API:

# my_module.py
import requests

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    response.raise_for_status()  # Raise an exception for bad status codes
    return response.json()

You want to test get_user_data without actually making a network request. Here’s how you’d do it with pytest-mock:

# test_my_module.py
import pytest
from unittest.mock import ANY
from my_module import get_user_data

def test_get_user_data_success(mocker):
    # Mock the requests.get function
    mock_get = mocker.patch('my_module.requests.get')

    # Configure the mock response
    mock_response = mock_get.return_value
    mock_response.json.return_value = {"id": 1, "name": "Alice"}
    mock_response.raise_for_status.return_value = None # Ensure no exception is raised

    # Call the function under test
    user_data = get_user_data(1)

    # Assertions
    mock_get.assert_called_once_with("https://api.example.com/users/1")
    assert user_data == {"id": 1, "name": "Alice"}

def test_get_user_data_api_error(mocker):
    mock_get = mocker.patch('my_module.requests.get')
    mock_response = mock_get.return_value
    mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found")

    with pytest.raises(requests.exceptions.HTTPError) as excinfo:
        get_user_data(2)

    mock_get.assert_called_once_with("https://api.example.com/users/2")
    assert "404 Not Found" in str(excinfo.value)

The core idea is that mocker.patch('my_module.requests.get') replaces the requests.get function within the my_module namespace with a mock object for the duration of the test. This mock object, mock_get, allows you to control its behavior (what it returns, what exceptions it raises) and assert how it was called.

The mocker fixture is provided by pytest-mock. It’s a convenience wrapper around unittest.mock. You can use mocker.patch in the same way you’d use unittest.mock.patch, but it automatically handles starting and stopping the patch for each test function.

When you use mocker.patch('my_module.requests.get'), you’re telling unittest.mock to find the get object within the requests module as it’s seen by my_module. This is crucial. If my_module had imported requests as import requests as req, you’d need to patch my_module.req.get. If my_module had done from requests import get, you’d patch my_module.get. The target of patch is always the name where the object is looked up by the code you’re testing.

The mock_get object returned by mocker.patch is an instance of unittest.mock.Mock (or MagicMock if you’re patching magic methods). You can set its return value using mock_get.return_value. This return value can itself be a mock object, allowing you to chain calls. In our example, mock_get.return_value is the mock response object, and we set its json method to return a dictionary and its raise_for_status method to do nothing.

unittest.mock also provides side_effect, which is powerful. Instead of returning a fixed value, side_effect can be a function or an exception. If it’s an exception, that exception will be raised when the mock is called. This is exactly what we do in test_get_user_data_api_error to simulate an API failure.

Assertions on mocks are key to verifying behavior. mock_get.assert_called_once_with(...) checks that the mock was called exactly once with the specified arguments. unittest.mock offers many assertion methods like assert_called(), assert_any_call(), assert_not_called(), and call_count. The ANY object from unittest.mock is useful when you don’t care about the specific value of an argument, as seen in assert_called_once_with(ANY).

The mocker fixture also has other useful methods. mocker.spy() can wrap an existing function, allowing you to assert calls to it without replacing its original behavior. mocker.stub() creates a simple mock object without patching anything in the system.

When patching, especially with complex import structures, understanding the lookup path is paramount. If my_module.py contains from requests import get, and your test file does from my_module import get_user_data, then inside get_user_data, get is looked up directly in the my_module’s namespace. To mock this, you’d patch my_module.get. If my_module.py has import requests, and get_user_data uses requests.get, then the lookup is requests.get within my_module, and you patch my_module.requests.get. Always patch where the object is looked up, not where it’s defined.

The next thing you’ll likely encounter is needing to mock multiple objects within a single test, or dealing with patches that need to be active across multiple test functions.

Want structured learning?

Take the full Pytest course →