Resource library

QA How-To

pytest monkeypatch Examples for Environment Variables and Config

Practical pytest monkeypatch examples for environment variables, config flags, import-time settings, and isolated fixtures, with runnable tests and fixes.

18 min read | 3,241 words

TL;DR

Use pytest's monkeypatch fixture to set or delete environment variables, update config mappings, and replace the exact attribute your code reads. Pytest restores patched process state after each test; import-time snapshots require separate handling.

Key Takeaways

  • Use setenv and delenv before calling code that reads environment variables.
  • Delete host variables explicitly when testing fallback behavior.
  • Use setitem for mutable configuration maps and setattr for attribute lookups.
  • Import-time constants need a controlled import or a better call-time design.
  • Keep monkeypatch-dependent fixtures function scoped for test isolation.
  • Run the whole suite to catch leaked state that a single test cannot reveal.

Pytest monkeypatch examples are most useful when a test must control environment variables, configuration dictionaries, or dependency lookups without changing the developer's machine. Use the built-in monkeypatch fixture to set an environment variable for one test, call the code under test, and let pytest restore the original state afterward. This tutorial builds a small configuration loader and tests its success, fallback, invalid input, and import-time behavior.

The examples use real pytest APIs: setenv, delenv, setitem, delitem, setattr, chdir, and context. The central distinction is when your application reads configuration. A function that calls os.getenv on every invocation behaves differently from a module constant initialized during import.

For a broader introduction to test discovery and assertions, read the pytest tutorial for beginners. This guide concentrates on replacing external configuration at the exact boundary your application reads.

What You Will Build

Create a two-file Python example: app_config.py implements a typed configuration loader, and test_config.py exercises it. Later, add frozen_config.py to demonstrate the special case of settings captured at import time. By the end, your suite will verify these behaviors:

  • Explicit endpoint, timeout, cache path, and token values override defaults.
  • Missing environment variables select documented fallbacks, while bad timeouts raise errors.
  • A mutable feature flag can be changed for one test and restored automatically.
  • Patches are applied to the lookup actually used by the application.
  • A reusable fixture gives each test an isolated configuration without hand-written cleanup.

Every example uses fake endpoints and tokens. It does not contact a service or require credentials.

Prerequisites

Use Python 3.12.7 and pytest 9.1.1 to reproduce the commands as checked for this tutorial. If your project already uses another supported release, match its installed Python and pytest versions instead of copying a package pin. The monkeypatch methods shown here are documented in the pytest monkeypatch guide. Run these commands from an empty project directory:

python3 --version
python3 -m pytest --version
python3 -m pip install 'pytest==9.1.1'

The first two commands should report your interpreter and pytest versions. Install pytest only when the second command says the module is missing. On Windows, substitute the interpreter command you use in your environment, such as py -3; the tests themselves are portable. The verification commands below assume files are in the current directory and that pytest can import them from there.

Step 1: Create a Configuration Loader for Pytest monkeypatch Examples

Start with code that reads values when load_config() is called. A default endpoint represents an offline test service; APP_TIMEOUT must be a positive integer; APP_CACHE_DIR is a path; and an empty token means no authentication. Define the feature flag separately so a later test can exercise dictionary patching. Save this as app_config.py:

import os
from dataclasses import dataclass
from pathlib import Path

FEATURE_FLAGS = {"resume_cache": True}

@dataclass(frozen=True)
class AppConfig:
    endpoint: str
    timeout_seconds: int
    cache_dir: Path
    token: str | None

def read_timeout() -> int:
    return int(os.getenv("APP_TIMEOUT", "5"))

def load_config() -> AppConfig:
    timeout = read_timeout()
    if timeout <= 0:
        raise ValueError("APP_TIMEOUT must be positive")
    return AppConfig(
        endpoint=os.getenv("APP_ENDPOINT", "https://api.example.test").rstrip("/"),
        timeout_seconds=timeout,
        cache_dir=Path(os.getenv("APP_CACHE_DIR", ".cache")).expanduser(),
        token=os.getenv("APP_TOKEN") or None,
    )

def is_cache_enabled() -> bool:
    return FEATURE_FLAGS["resume_cache"]

Create test_config.py with its shared imports and the first test. Keep adding later snippets below this test in the same file. The explicit env values ensure the assertion does not depend on whatever is configured in your shell or CI worker:

import os
from pathlib import Path

import pytest

import app_config

def test_explicit_config(monkeypatch):
    monkeypatch.setenv("APP_ENDPOINT", "https://stub.example.test/")
    monkeypatch.setenv("APP_TIMEOUT", "12")
    monkeypatch.setenv("APP_CACHE_DIR", "/tmp/qa-cache")
    monkeypatch.setenv("APP_TOKEN", "fake-token")

    config = app_config.load_config()

    assert config.endpoint == "https://stub.example.test"
    assert config.timeout_seconds == 12
    assert config.cache_dir == Path("/tmp/qa-cache")
    assert config.token == "fake-token"

setenv writes a string into os.environ and registers the original value for restoration. It can replace an existing variable or create a new one. The loader trims a trailing slash from the endpoint, so this first assertion also checks a transformation rather than merely echoing the input. Do not store a real secret in the test: fake-token tests parsing without exposing one.

Verify: Run python3 -m pytest -q test_config.py. Expect 1 passed. If pytest cannot import app_config, check that both files are in the directory from which you issued the command.

Step 2: Test Missing Values With Pytest monkeypatch Examples

A machine may already have APP_TIMEOUT or APP_TOKEN set. Testing defaults without first removing those variables creates a test that passes locally and fails in CI, or the reverse. Append these tests to test_config.py:

def test_defaults_when_environment_is_missing(monkeypatch):
    for name in ("APP_ENDPOINT", "APP_TIMEOUT", "APP_CACHE_DIR", "APP_TOKEN"):
        monkeypatch.delenv(name, raising=False)

    config = app_config.load_config()

    assert config.endpoint == "https://api.example.test"
    assert config.timeout_seconds == 5
    assert config.cache_dir == Path(".cache")
    assert config.token is None

def test_empty_token_is_treated_as_absent(monkeypatch):
    monkeypatch.setenv("APP_TOKEN", "")
    assert app_config.load_config().token is None

delenv(name, raising=False) is intentional. By default, deleting a name that is already absent raises KeyError; this test accepts either initial state and then proves the fallback. It tests a different condition from an empty string. os.getenv("APP_TOKEN") or None maps both an unset token and "" to None, while APP_TIMEOUT="" would fail integer parsing. Pytest restores variables after each test, including variables removed with delenv. The restoration happens even when an assertion fails. Within a test, though, each patch remains active until teardown or the end of a scoped context. For larger suites that load .env files, see dotenv configuration for QA tests: precedence between shell, file, and test patches needs to be explicit.

Verify: Run python3 -m pytest -q test_config.py. Expect 3 passed. To prove the default test ignores host settings, run APP_TIMEOUT=99 python3 -m pytest -q test_config.py on a POSIX shell. It should still report 3 passed because the test removes that variable before calling load_config().

Step 3: Cover Invalid and Boundary Configuration

A useful configuration test does more than prove one happy value. int() rejects nonnumeric text, and our loader rejects zero and negative timeouts after conversion. Parameterize the inputs so the test report identifies the specific case. Append this code:

@pytest.mark.parametrize(
    ("raw_timeout", "expected_error"),
    [
        ("0", ValueError),
        ("-3", ValueError),
        ("not-a-number", ValueError),
        ("", ValueError),
    ],
)
def test_invalid_timeout(monkeypatch, raw_timeout, expected_error):
    monkeypatch.setenv("APP_TIMEOUT", raw_timeout)
    with pytest.raises(expected_error):
        app_config.load_config()

def test_timeout_uses_whole_seconds(monkeypatch):
    monkeypatch.setenv("APP_TIMEOUT", "1")
    assert app_config.load_config().timeout_seconds == 1

These cases deliberately have the same exception class but different causes. For "0" and "-3", parsing succeeds and the positive-value rule raises. For "not-a-number" and "", int() raises before the rule is reached. If you need a stable, user-facing error message, catch the parsing error in production code and re-raise a domain-specific exception; do not make a test assert an error message the application has not promised. The boundary case "1" establishes the smallest accepted positive integer in this implementation.

@pytest.mark.parametrize creates independent test cases. The monkeypatch fixture is function scoped, so one row's value is undone before the next row runs. You do not need a cleanup loop after the assertion. Verify: Run python3 -m pytest -q test_config.py. Expect 8 passed: three earlier tests, four invalid inputs, and one accepted boundary. Run python3 -m pytest -q test_config.py -k invalid_timeout to inspect only the four negative cases.

Step 4: Control Cache Paths and the Working Directory

Configuration sometimes combines an environment variable with the current directory. monkeypatch.chdir() changes the process working directory for one test and restores it afterward. Use pytest's tmp_path fixture to keep any generated files away from the repository. Append:

def test_relative_cache_dir_uses_temporary_working_directory(monkeypatch, tmp_path):
    monkeypatch.chdir(tmp_path)
    monkeypatch.delenv("APP_CACHE_DIR", raising=False)

    config = app_config.load_config()
    resolved_cache = config.cache_dir.resolve()

    assert Path.cwd() == tmp_path
    assert resolved_cache == tmp_path / ".cache"

def test_absolute_cache_dir_from_environment(monkeypatch, tmp_path):
    chosen = tmp_path / "cache"
    monkeypatch.setenv("APP_CACHE_DIR", str(chosen))
    assert app_config.load_config().cache_dir == chosen

The loader returns Path(".cache") for the default, which is relative. Calling .resolve() while the patched working directory is active gives the full path under tmp_path. The second test covers an absolute value and converts the Path to a string because environment variables are strings. This is a common source of warnings or platform surprises: setenv accepts a string value, so use str(path) when the input is a Path.

Avoid assertions that hard-code your developer home directory. If the product specifically expands ~, test that behavior separately with a controlled home directory and the platform semantics your deployment supports. Changing the working directory does not substitute for changing home-directory lookup. Verify: Run python3 -m pytest -q test_config.py. Expect 10 passed. If the first test fails because of a path difference, print config.cache_dir and Path.cwd() in that test; the returned relative path and its resolved location have different representations by design.

Step 5: Patch a Configuration Dictionary Without Replacing It

Many applications keep flags in a module-level dictionary. setitem changes one key and records the previous value. delitem removes a key and can avoid an exception when the key is absent. Add the following tests:

def test_disable_cache_for_one_test(monkeypatch):
    monkeypatch.setitem(app_config.FEATURE_FLAGS, "resume_cache", False)
    assert app_config.is_cache_enabled() is False

def test_missing_cache_flag_raises(monkeypatch):
    monkeypatch.delitem(app_config.FEATURE_FLAGS, "resume_cache")
    with pytest.raises(KeyError, match="resume_cache"):
        app_config.is_cache_enabled()

def test_cache_flag_has_original_value():
    assert app_config.is_cache_enabled() is True

The final test documents the original value, but it is not a substitute for pytest's isolation: a direct assignment such as FEATURE_FLAGS["resume_cache"] = False would leave the flag changed for later tests. With setitem and delitem, pytest returns it to True at teardown. The restoration is to the prior value, even if a different suite intentionally set it to something else before the patch. Use delitem(mapping, key, raising=False) when missing keys are an allowed starting state. Here the key is part of the application's defined configuration, so the default raising=True exposes a misspelled name. If the flag is read through another module's copied value, patch that module's reference instead; changing this dictionary helps only code that actually reads this dictionary.

Verify: Run python3 -m pytest -q test_config.py. Expect 13 passed. Add -k cache_flag to inspect the dictionary cases. If the last test fails only when the full suite runs, look for another test that mutates FEATURE_FLAGS directly and leaves state behind.

Step 6: Distinguish Call-Time Reads From Import-Time Reads

app_config.load_config() reads the environment when called. Some code takes a snapshot during module import instead. Patching the environment after import will not update that stored constant. Create frozen_config.py:

import os

BOOT_TIMEOUT = int(os.getenv("APP_TIMEOUT", "5"))

def boot_timeout() -> int:
    return BOOT_TIMEOUT

Append this test, including the new import near the other imports in test_config.py:

import importlib

import frozen_config

def test_import_time_setting_requires_reload(monkeypatch):
    original_timeout = frozen_config.boot_timeout()
    chosen_timeout = original_timeout + 1
    try:
        with monkeypatch.context() as scoped:
            scoped.setenv("APP_TIMEOUT", str(chosen_timeout))
            assert frozen_config.boot_timeout() == original_timeout
            importlib.reload(frozen_config)
            assert frozen_config.boot_timeout() == chosen_timeout
    finally:
        importlib.reload(frozen_config)

The chosen timeout differs from the module's original value, so the pre-reload assertion proves the constant stays stale. The strong check after reload proves the module body executes under the patched environment. monkeypatch.context() restores the environment as its block exits; the finally reload then returns the module constant to the current environment's value even if an assertion fails. Reloading can re-run initialization and leave references imported elsewhere unchanged.

A better production design often delays the read until a function call, as load_config() does, or passes an explicit settings object to consumers. If import-time capture is a requirement, patch before the first import in a narrowly scoped test and take care with sys.modules. Do not assume changing os.environ retroactively updates values that were copied into module globals. The pytest versus unittest comparison offers context on fixture-based setup versus mock patchers.

Verify: Run python3 -m pytest -q test_config.py -k import_time. Expect 1 passed; then run the whole file and expect 14 passed. If reloading fails because application imports have side effects, that is a reason to refactor the configuration boundary rather than broaden the patch to pytest internals.

Step 7: Patch the Name the Application Actually Looks Up

load_config() calls read_timeout() by looking up that name in the app_config module. Therefore, patch app_config.read_timeout, not a different alias that the loader never consults. Append:

def test_replace_timeout_reader(monkeypatch):
    monkeypatch.setattr(app_config, "read_timeout", lambda: 17)
    assert app_config.load_config().timeout_seconds == 17

def test_misspelled_timeout_reader_is_rejected(monkeypatch):
    with pytest.raises(AttributeError):
        monkeypatch.setattr(app_config, "read_timout", lambda: 17)

The second test preserves setattr's default raising=True behavior. A typo should fail immediately instead of silently creating an attribute that the application never reads. raising=False is useful only when intentionally adding a new attribute for the duration of a test. That is unusual for a fixed configuration API.

If another module uses from app_config import read_timeout, it owns a separate local reference to that function. A later patch to app_config.read_timeout will not replace that imported reference. Patch the name in the consumer module or redesign the consumer to accept a function or settings object. The official pytest monkeypatch documentation describes this rule as patching where the name is used.

Verify: Run python3 -m pytest -q test_config.py -k timeout_reader. Expect 2 passed; the full file should report 16 passed. If the first test still returns a value from APP_TIMEOUT, inspect the function called by load_config() and patch that exact module attribute.

Step 8: Reuse a Function-Scoped Configuration Fixture

When several tests need the same environment, collect the arrangement in a function-scoped fixture. Keep each test focused on the behavior it checks. Add this to test_config.py:

@pytest.fixture
def local_config(monkeypatch, tmp_path):
    monkeypatch.setenv("APP_ENDPOINT", "https://local.example.test/")
    monkeypatch.setenv("APP_TIMEOUT", "7")
    monkeypatch.setenv("APP_CACHE_DIR", str(tmp_path / "cache"))
    monkeypatch.delenv("APP_TOKEN", raising=False)
    return app_config.load_config()

def test_local_config_endpoint(local_config):
    assert local_config.endpoint == "https://local.example.test"

def test_local_config_cache_path(local_config, tmp_path):
    assert local_config.cache_dir == tmp_path / "cache"
    assert local_config.timeout_seconds == 7
    assert local_config.token is None

A test requests local_config by naming it in its arguments. Pytest resolves monkeypatch and tmp_path for the fixture, builds the AppConfig, and tears those dependencies down after the test. The fixture runs separately for the two tests, giving each a fresh temporary directory and patch stack. Do not make this fixture session scoped: the built-in monkeypatch fixture is function scoped and cannot be injected into a broader-scope fixture.

Decide whether a fixture should return parsed configuration or merely set the environment. Returning the object is convenient for consumers that inspect values; yielding after setup can support extra teardown if your fixture also creates files or connections. Keep unrelated settings out of the shared fixture, or a change to one test's expected setup can obscure failures elsewhere.

Verify: Run python3 -m pytest -q test_config.py. Expect 18 passed. Run python3 -m pytest -q test_config.py -k local_config to verify just the two fixture consumers. A ScopeMismatch error usually means the custom fixture was given scope="module" or scope="session" while still requesting function-scoped monkeypatch.

Choosing the Right Monkeypatch Method

The method should match the storage or lookup boundary. This table summarizes the examples above and the restoration pytest performs:

Need Method Target What is restored
Supply an env value setenv("APP_TIMEOUT", "7") os.environ Previous value or absence
Test an absent env value delenv("APP_TOKEN", raising=False) os.environ Previous value if one existed
Change one flag setitem(FEATURE_FLAGS, "resume_cache", False) Mutable mapping Previous item value or absence
Remove one flag delitem(FEATURE_FLAGS, "resume_cache") Mutable mapping Previous item value
Replace a lookup setattr(app_config, "read_timeout", fn) Object attribute Previous attribute
Resolve relative files chdir(tmp_path) Process working directory Previous directory
End a patch early context() Nested patch collection State at block exit

setenv does not patch an arbitrary configuration object. setattr substitutes a reference, so its success depends on how the application imported the dependency. context() is especially helpful when temporarily replacing a standard-library function that pytest itself might use after your assertion. Keep that patch inside the smallest possible block.

None of these methods makes global process mutation safe across threads. If application code reads os.environ concurrently in the same process, another thread can observe a test value while it is active. Prefer explicit dependency injection for concurrent code, and keep environment patching to tests whose relevant work happens synchronously within the test.

Troubleshooting

Problem: A default-value test passes locally but fails in CI. -> Remove each relevant variable with monkeypatch.delenv(name, raising=False) before calling the loader. CI may inject service URLs or tokens. Also check whether your application loaded a .env file after the patch and overwrote it. Record the intended precedence in one place rather than guessing which source wins.

Problem: setenv appears to have no effect. -> Find the moment the code reads the variable. If it creates a module constant at import, patch before import or refactor to a call-time loader. If the code calls a wrapper function, patch that wrapper or pass in a settings object. A value already copied into a dataclass cannot change just because os.environ changed.

Problem: monkeypatch.setattr raises AttributeError. -> Confirm the exact attribute spelling and module where the consumer looks it up. Keep the default raising=True while debugging. Switching it off may produce a passing setup step and a failing behavior assertion, which hides the typo.

Problem: setenv warns about a non-string value. -> Convert integers or Path objects explicitly: str(tmp_path) or "7". Environment variables are text, and the conversion belongs at the test boundary. The loader should parse the text into the required type.

Problem: A fixture reports ScopeMismatch. -> Use the default function scope for a fixture that requests monkeypatch. A broader-scope fixture cannot depend on a narrower one. If a costly shared resource truly needs session scope, keep it separate from per-test environment overrides.

Problem: A test leaves an import-time constant changed for later tests. -> Restore the environment, then reload the affected module in finally, as the example shows. For code with extensive import side effects, move parsing to an explicit function instead of relying on reload.

Interview Questions and Answers

These concise questions test whether you can explain the isolation boundary, not merely recall method names. Q: Why use monkeypatch.setenv instead of assigning os.environ directly? Pytest records the original value and restores it after the test, including when the test fails. Direct assignment needs your own cleanup and can contaminate another case.

Q: How do you test a missing environment variable when it may already exist on CI? Call monkeypatch.delenv(name, raising=False) before invoking the code. The flag allows either starting state while ensuring the branch under test sees absence.

Q: Why might changing APP_TIMEOUT not affect a test? The code may have read it during import and stored it in a module constant. Identify when the read occurs, then patch before that boundary or make configuration loading explicit.

Q: What is the difference between setitem and setattr? setitem changes a key in a mutable mapping; setattr replaces an attribute on an object or module. Choose according to how production code performs its lookup.

Q: What does raising=False do? It allows a missing target for delenv, delitem, or setattr where supported, instead of throwing immediately. Use it when absence is expected, not to conceal misspelled names.

Q: Why keep a monkeypatch-dependent fixture function scoped? The built-in fixture is function scoped and restores state after each test. A session-scoped fixture cannot depend on it and would weaken isolation even if implemented another way.

Common Mistakes

  • Patching a source module after another module imported a name from it. Find the consumer's actual lookup and patch there.
  • Assigning os.environ or a shared dictionary directly, then relying on test order or an assertion path for cleanup. Use the corresponding monkeypatch method.
  • Testing a fallback without removing the host variable first. An inherited CI value can silently choose the other branch.
  • Treating "" and an unset variable as interchangeable for all settings. This loader treats an empty token as absent but an empty timeout as invalid.
  • Patching built-ins or broad standard-library functions for an entire test. Limit such changes with monkeypatch.context() and prefer a narrow application boundary.
  • Logging real tokens to explain failures. Use obvious fake values in unit tests and follow your organization's CI secrets management practices for integration tests.

Where To Go Next

Apply the same controlled-input approach to a real project configuration loader. First list every environment name the loader reads, then add one test for a supplied value, a missing value, and each meaningful invalid value. If the config is loaded from files as well, combine tmp_path with a deliberate working directory and document the source precedence.

Continue with the pytest tutorial for beginners for fixtures and parametrization, the dotenv test configuration guide for file-backed settings, and the CI secrets management guide when tests require protected credentials. The DevOps for QA roadmap places configuration isolation alongside CI execution and repeatable environments. For interview preparation, compare fixture cleanup with mock patchers in pytest versus unittest.

Conclusion

Pytest monkeypatch examples become reliable when each patch targets the boundary the application actually reads. Set or delete environment variables before calling a runtime loader, use mapping and attribute methods for their respective lookups, and treat import-time constants as a separate design problem. Run the complete suite after adding a new patch so a passing single test does not hide shared state. Then move the most repeated setup into a function-scoped fixture and keep each assertion tied to one configuration rule.

Interview Questions and Answers

Why is monkeypatch safer than assigning os.environ directly in a test?

Monkeypatch records the previous state and restores it during fixture teardown, even when the test fails. Direct mutation requires a reliable finally block. I still patch before the code reads the variable and assert the resulting behavior rather than the environment assignment alone.

How do you test a fallback when CI may set the same environment variable?

I remove that variable with monkeypatch.delenv(name, raising=False) in the test's arrange phase. The flag accepts either initial state, while the application sees a guaranteed absence. Then I assert the documented fallback and run the test under a shell value as an extra check.

What happens when a Python module reads an environment variable at import time?

The imported module stores a snapshot, so changing os.environ later will not update that constant. For a focused test, I patch before importing or reload under a scoped patch and restore afterward. In application code I prefer an explicit load_config function because it makes the read boundary testable.

How do you decide whether to use setitem or setattr?

I inspect the production lookup. Dictionary indexing calls for setitem, while a module or object attribute lookup calls for setattr. For imported functions I patch the name in the consumer module, since replacing the original module's attribute may not affect an already imported alias.

What does raising=False change in monkeypatch operations?

It allows certain operations to proceed when a target is absent. For example, delenv with raising=False safely enforces an absent variable. I keep the default raising=True for known attributes and keys so spelling mistakes fail at setup rather than silently leaving the behavior unchanged.

Why can't a session-scoped fixture depend on monkeypatch?

The built-in monkeypatch fixture is function scoped. A wider-scoped fixture cannot depend on a narrower-scoped one because its lifetime would outlast the dependency. I keep per-test config setup function scoped and separate shared resources into their own fixtures.

How would you test both empty and missing token values?

I use delenv to test a truly missing token and setenv with an empty string to test an explicitly empty value. These are distinct inputs, even if the loader intentionally maps both to None. I document that mapping in assertions so a future parser change is deliberate.

Frequently Asked Questions

Does pytest monkeypatch restore environment variables after a failed test?

Yes. The fixture registers each change and restores the prior value, or removes a newly created variable, during teardown even if an assertion fails. The patch remains active through the rest of that test unless you use monkeypatch.context() for a shorter scope.

How do I test an environment variable that is not set?

Call monkeypatch.delenv("NAME", raising=False) before invoking the code under test. This handles both a variable already absent and one inherited from your shell or CI. Assert the application's fallback or error, not merely that the variable disappeared.

What is the difference between setenv and setitem?

setenv changes a process environment variable through os.environ and is clear about text-based configuration. setitem changes one key in any mutable mapping, such as a feature-flag dictionary. Both record the previous state for restoration.

Why does monkeypatch.setenv not change my imported config constant?

A constant assigned during module import already contains the earlier value. Changing os.environ does not recalculate that assignment. Prefer loading config when needed, or patch before import and carefully manage module state in the test.

Can I use monkeypatch in a session-scoped fixture?

Not by requesting the built-in monkeypatch fixture: it has function scope, and pytest rejects that wider-scope dependency. Keep environment setup function scoped. Separate genuinely shared resources from per-test config overrides.

When should I use monkeypatch.context()?

Use it when a patch should end before the test ends, particularly for changes that could affect pytest or subsequent assertions. Patches made through the context object are undone when the with block exits. The outer monkeypatch fixture remains available.

Should I pass an integer directly to monkeypatch.setenv?

Pass a string such as "7". Environment variables are text, and passing a non-string value can trigger a pytest warning and implicit conversion. Convert Path objects with str(path) and parse values inside the application.

Related Guides