Pytest Monkeypatch: Safely Alter Attributes & Env Vars
The monkeypatch fixture in pytest provides
a secure, isolated mechanism to dynamically modify classes, modules,
dictionaries, and environment variables during test execution. This
article explains how monkeypatch operates under the hood,
how its built-in teardown prevents test pollution, and how to use its
core methods to manipulate system attributes and runtime configurations
safely.
How Monkeypatch Ensures Safety
When tests modify global state—such as os.environ or
attributes on imported modules—those modifications persist in memory
across the entire test suite unless manually undone. If a test fails
before cleanup occurs, subsequent tests inherit an unpredictable
environment, causing flaky and cascading failures.
The monkeypatch fixture prevents this by managing state
through pytest's built-in fixture lifecycle. It maintains an internal
stack of changes. Whenever you modify an attribute or variable using
monkeypatch, the fixture records the original value. Once
the test function completes—regardless of whether it passed, failed, or
raised an unhandled exception—monkeypatch automatically
runs a teardown routine that restores all modified attributes and
variables to their exact pre-test states.
Modifying Environment Variables
Python stores environment variables in the os.environ
mapping. Instead of directly editing this dictionary,
monkeypatch offers dedicated methods:
monkeypatch.setenv(name, value, prepend=None): Sets an environment variable to a new string value. Ifprependis specified, it prepends the value to the existing variable using the system's path separator.monkeypatch.delenv(name, raising=True): Removes an environment variable from the system. Settingraising=Falseprevents an error if the key does not already exist.
import os
def get_database_url():
return os.getenv("DATABASE_URL", "sqlite:///:memory:")
def test_custom_database_url(monkeypatch):
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost:5432/test_db")
assert get_database_url() == "postgresql://localhost:5432/test_db"
def test_default_database_url(monkeypatch):
monkeypatch.delenv("DATABASE_URL", raising=False)
assert get_database_url() == "sqlite:///:memory:"Once each test finishes, os.environ is reverted to its
original system state.
Modifying System Attributes and Functions
The monkeypatch fixture allows you to substitute
attributes, methods, or entire classes across standard libraries and
application code using setattr and
delattr.
monkeypatch.setattr(target, name, value, raising=True): Replaces an attribute on an object or module. By default, it raises anAttributeErrorif the attribute does not exist, ensuring you do not patch non-existent targets.monkeypatch.delattr(target, name, raising=True): Deletes an attribute from an object or module for the duration of the test.
This is frequently used to alter system-level configurations such as
sys.argv or platform identifiers:
import sys
def test_cli_arguments(monkeypatch):
test_args = ["program_name", "--export", "output.csv"]
monkeypatch.setattr(sys, "argv", test_args)
assert sys.argv == test_argsYou can also provide a string import path instead of a direct object reference:
def test_patched_function(monkeypatch):
monkeypatch.setattr("app.services.fetch_data", lambda: {"status": "ok"})
from app.services import fetch_data
assert fetch_data() == {"status": "ok"}Modifying Dictionaries and System Paths
Beyond attributes and environment variables, monkeypatch
includes helpers for dictionaries and the Python import path:
monkeypatch.setitem(dic, name, value)&monkeypatch.delitem(dic, name, raising=True): Safely mutates dictionary entries, ensuring configurations or cached data structures are cleanly restored.monkeypatch.syspath_prepend(path): Adds a directory to the beginning ofsys.pathso the runtime can locate test-specific modules without permanently modifying the interpreter search path.monkeypatch.chdir(path): Safely changes the current working directory for file-system-dependent tests and restores the original path upon completion.
By consolidating these operations into a single lifecycle-aware
fixture, monkeypatch eliminates manual cleanup code and
guarantees state isolation across your test suite.