This rule raises an issue when test functions manually modify global state such as environment variables, object attributes, or module search paths without using test isolation fixtures or mocking utilities provided by the test framework.

In Python/pytest, this specifically refers to using the monkeypatch fixture instead of directly modifying sys.path or other global state.

Why is this an issue?

When tests manually modify global state, they risk polluting the test environment for subsequent tests. This happens because the modified state persists beyond the test that changed it, unless the test explicitly restores the original values.

Manual cleanup has several problems:

Test utilities that manage state modifications solve these problems by automatically undoing all modifications when the test completes, regardless of whether the test passes or fails. They provide a clean, declarative API that makes the intent clear and eliminates the need for manual cleanup code.

What is the potential impact?

State pollution from missed cleanup propagates across the suite, so failures show up in unrelated tests and erode trust in green CI runs.

How to fix it

Declare the monkeypatch fixture as a test parameter and use its API instead of manual save/restore logic:

To replace a function or method, pass the attribute name as a string to monkeypatch.setattr and supply a stub function or lambda as the replacement:

Use a named stub function when the replacement is reused or needs more than one statement; use a lambda for simple one-line return values.

Each method automatically undoes the modification after the test completes, even when the test fails.

For straightforward temporary changes to attributes, environment variables, or callables on existing modules and objects, prefer monkeypatch.

unittest.mock.patch is a better fit when you need to mock entire modules, assert call arguments with Mock.assert_called_with, configure complex side effects, or nest several patches with context managers. Review manually before replacing working patch-based code with monkeypatch.

Code examples

Noncompliant code example

import os

def test_api_key():
    old_key = os.environ.get('API_KEY')
    os.environ['API_KEY'] = 'test_key'  # Noncompliant

    result = get_api_configuration()
    assert result['key'] == 'test_key'

    # Cleanup easily forgotten or skipped on exception
    if old_key:
        os.environ['API_KEY'] = old_key
    else:
        del os.environ['API_KEY']

Compliant solution

def test_api_key(monkeypatch):
    monkeypatch.setenv('API_KEY', 'test_key')

    result = get_api_configuration()
    assert result['key'] == 'test_key'

    # Automatic cleanup - no manual code needed

Noncompliant code example

import myapp.config

def test_feature_flag():
    old_value = myapp.config.ENABLE_FEATURE
    myapp.config.ENABLE_FEATURE = True  # Noncompliant

    assert myapp.is_feature_enabled()

    myapp.config.ENABLE_FEATURE = old_value

Compliant solution

def test_feature_flag(monkeypatch):
    monkeypatch.setattr(myapp.config, 'ENABLE_FEATURE', True)

    assert myapp.is_feature_enabled()

Noncompliant code example

import myapp.client

def test_notify_user():
    client = myapp.client.Client()
    original_send = client.send
    client.send = lambda message: {"sent": True}  # Noncompliant

    result = client.notify("Hello")

    assert result["sent"]
    client.send = original_send

Compliant solution

def test_notify_user(monkeypatch):
    client = myapp.client.Client()
    monkeypatch.setattr(client, "send", lambda message: {"sent": True})

    result = client.notify("Hello")

    assert result["sent"]

Resources

Documentation