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:
Object attributes and methods
Dictionary keys and values
Environment variables
The Python import path (
sys.path)The current working directory
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
Automatic Reversion: All monkeypatch modifications are tracked and undone at test teardown.
Safety: The
raisingparameter controls whether missing attributes or keys raise exceptions or are silently ignored.Integration:
syspath_prependcallspkg_resources.fixup_namespace_packagesandimportlib.invalidate_cachesto ensure imports behave correctly after patching.Isolation: Designed primarily for use within
pytesttests and fixtures to maintain test isolation and reproducibility.
Interactions with Other System Components
pytestFramework:monkeypatchis a built-in fixture inpytest, tightly integrated into the test lifecycle.Python Standard Library: It can patch stdlib modules, environment variables, and system paths.
Third-Party Libraries: Commonly used to mock external modules like
requestsor filesystem operations.User Tests and Fixtures: Enables test authors to create isolated, deterministic test environments by patching dependencies dynamically.
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
pytestOfficial Documentation: https://docs.pytest.org/en/stable/monkeypatch.htmlBlog post on monkeypatching: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/