monkeypatch.rst

Overview

The [monkeypatch.rst](/projects/286/67332) file is a comprehensive documentation page that explains the concept, usage, and best practices for **monkeypatching** in the context of the `pytest` testing framework. Monkeypatching is a technique used in testing to dynamically modify or replace attributes, environment variables, dictionary entries, or system paths during test execution, allowing tests to isolate dependencies, mock external behavior, or control global state without permanent side effects.

This file details how the `monkeypatch` fixture in `pytest` can be used to safely and temporarily patch:

All patches are automatically reverted after the test or fixture finishes, ensuring test isolation and preventing side effects.


Detailed Explanation of Key Concepts and Methods

The monkeypatch Fixture

`monkeypatch` is a built-in `pytest` fixture that provides a clean interface to apply temporary patches during test execution. It exposes several key methods:

Method

Description

`setattr(obj, name, value, raising=True)`

Set an attribute `name` on `obj` to `value`. Raises [AttributeError](/projects/286/67579) if attribute missing and `raising=True`.

`delattr(obj, name, raising=True)`

Delete an attribute `name` from `obj`. Raises [AttributeError](/projects/286/67579) if attribute missing and `raising=True`.

`setitem(mapping, name, value)`

Set the dictionary key `name` to `value` in `mapping`.

`delitem(mapping, name, raising=True)`

Delete the dictionary key `name` from `mapping`. Raises `KeyError` if missing and `raising=True`.

`setenv(name, value, prepend=None)`

Set an environment variable `name` to `value`. Optionally prepend to existing value using `prepend`.

`delenv(name, raising=True)`

Delete an environment variable `name`. Raises `KeyError` if missing and `raising=True`.

`syspath_prepend(path)`

Insert `path` at the front of `sys.path` and fix namespace packages.

`chdir(path)`

Change the current working directory to `path`.

`context()`

Context manager to limit the scope of patches to a block of code (useful for stdlib patching).

All modifications made via these methods are reverted automatically after the test or fixture completes.


Usage Examples

1. Monkeypatching Functions or Class Properties

Modify a function or class attribute during a test to simulate a controlled environment or behavior.

from pathlib import Path

def getssh():
    return Path.home() / ".ssh"

def test_getssh(monkeypatch):
    def mockreturn():
        return Path("/abc")

    monkeypatch.setattr(Path, "home", mockreturn)
    assert getssh() == Path("/abc/.ssh")

This replaces `Path.home()` with a mock function returning a fixed path, isolating the test from the actual user environment.


2. Monkeypatching Returned Objects with Mock Classes

Mock complex return objects by creating a mock class that simulates the expected interface.

import requests
import app  # module with get_json function

class MockResponse:
    @staticmethod
    def json():
        return {"mock_key": "mock_response"}

def test_get_json(monkeypatch):
    def mock_get(*args, **kwargs):
        return MockResponse()

    monkeypatch.setattr(requests, "get", mock_get)
    result = app.get_json("https://fakeurl")
    assert result["mock_key"] == "mock_response"

This approach can be factored into fixtures to share mock behavior across multiple tests.


3. Global Patch Example: Blocking HTTP Requests

Prevent external HTTP calls in all tests by deleting the HTTP request method globally.

# conftest.py
import pytest

@pytest.fixture(autouse=True)
def no_requests(monkeypatch):
    monkeypatch.delattr("requests.sessions.Session.request")

This fixture runs automatically for all tests, making real HTTP requests impossible, ensuring test isolation.


4. Monkeypatching Environment Variables

Modify or delete environment variables safely during tests.

import os
import pytest

def get_os_user_lower():
    username = os.getenv("USER")
    if username is None:
        raise OSError("USER environment is not set.")
    return username.lower()

def test_upper_to_lower(monkeypatch):
    monkeypatch.setenv("USER", "TestingUser")
    assert get_os_user_lower() == "testinguser"

def test_raise_exception(monkeypatch):
    monkeypatch.delenv("USER", raising=False)
    with pytest.raises(OSError):
        get_os_user_lower()

Fixtures can be used to modularize environment variable patches.


5. Monkeypatching Dictionaries

Modify dictionary contents temporarily during tests.

import app  # module with DEFAULT_CONFIG dict

def test_connection(monkeypatch):
    monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
    monkeypatch.setitem(app.DEFAULT_CONFIG, "database", "test_db")
    expected = "User Id=test_user; Location=test_db;"
    assert app.create_connection_string() == expected

def test_missing_user(monkeypatch):
    monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
    import pytest
    with pytest.raises(KeyError):
        app.create_connection_string()

Fixtures can be created for common dictionary patches and reused.


6. Using monkeypatch.context() for Scoped Patching

To avoid breaking pytest internals when patching stdlib or third-party libraries, use the context manager:

import functools

def test_partial(monkeypatch):
    with monkeypatch.context() as m:
        m.setattr(functools, "partial", 3)
        assert functools.partial == 3

The patch only applies inside the `with` block.


Implementation Details


Interactions with Other System Components


Visual Diagram: Utility Flowchart of monkeypatch Methods

flowchart TD
    A[monkeypatch fixture] --> B[setattr(obj, name, value, raising=True)]
    A --> C[delattr(obj, name, raising=True)]
    A --> D[setitem(mapping, name, value)]
    A --> E[delitem(mapping, name, raising=True)]
    A --> F[setenv(name, value, prepend=None)]
    A --> G[delenv(name, raising=True)]
    A --> H[syspath_prepend(path)]
    A --> I[chdir(path)]
    A --> J[context()]

    B --> K[Patch object attribute]
    C --> L[Remove object attribute]
    D --> M[Patch dictionary item]
    E --> N[Remove dictionary item]
    F --> O[Set environment variable]
    G --> P[Delete environment variable]
    H --> Q[Modify sys.path]
    I --> R[Change current working directory]
    J --> S[Scoped patching context]

Summary

This documentation provides a detailed guide on how to use the `monkeypatch` fixture in `pytest` to mock and patch various aspects of the runtime environment during tests. It covers patching functions, classes, returned objects, environment variables, dictionaries, and system paths, along with best practices such as scoped patching to avoid interfering with `pytest` internals.

By leveraging `monkeypatch`, test authors can write more reliable, isolated, and maintainable tests that do not depend on external systems or global state.


References