Monkeypatching/mocking modules and environments¶
Sometimes tests need to invoke functionality which depends
on global settings or which invokes code which cannot be easily
tested such as network access. The monkeypatch
fixture
helps you to safely set/delete an attribute, dictionary item or
environment variable or to modify sys.path
for importing.
See the monkeypatch blog post for some introduction material
and a discussion of its motivation.
Simple example: monkeypatching functions¶
If you want to pretend that os.expanduser
returns a certain
directory, you can use the monkeypatch.setattr()
method to
patch this function before calling into a function which uses it:
# content of test_module.py
import os.path
def getssh(): # pseudo application code
return os.path.join(os.path.expanduser("~admin"), '.ssh')
def test_mytest(monkeypatch):
def mockreturn(path):
return '/abc'
monkeypatch.setattr(os.path, 'expanduser', mockreturn)
x = getssh()
assert x == '/abc/.ssh'
Here our test function monkeypatches os.path.expanduser
and
then calls into a function that calls it. After the test function
finishes the os.path.expanduser
modification will be undone.
Global patch example: preventing “requests” from remote operations¶
If you want to prevent the “requests” library from performing http requests in all your tests, you can do:
# content of conftest.py
import pytest
@pytest.fixture(autouse=True)
def no_requests(monkeypatch):
monkeypatch.delattr("requests.sessions.Session.request")
This autouse fixture will be executed for each test function and it
will delete the method request.session.Session.request
so that any attempts within tests to create http requests will fail.
Note
Be advised that it is not recommended to patch builtin functions such as open
,
compile
, etc., because it might break pytest’s internals. If that’s
unavoidable, passing --tb=native
, --assert=plain
and --capture=no
might
help although there’s no guarantee.
Note
Mind that patching stdlib
functions and some third-party libraries used by pytest
might break pytest itself, therefore in those cases it is recommended to use
MonkeyPatch.context()
to limit the patching to the block you want tested:
import functools
def test_partial(monkeypatch):
with monkeypatch.context() as m:
m.setattr(functools, "partial", 3)
assert functools.partial == 3
See issue #3290 for details.
Monkeypatching environment variables¶
If you are working with environment variables you often need to safely change the values
or delete them from the system for testing purposes. Monkeypatch
provides a mechanism
to do this using the setenv
and delenv
method. Our example code to test:
# contents of our original code file e.g. code.py
import os
def get_os_user_lower():
"""Simple retrieval function.
Returns lowercase USER or raises EnvironmentError."""
username = os.getenv("USER")
if username is None:
raise EnvironmentError("USER environment is not set.")
return username.lower()
There are two potential paths. First, the USER
environment variable is set to a
value. Second, the USER
environment variable does not exist. Using monkeypatch
both paths can be safely tested without impacting the running environment:
# contents of our test file e.g. test_code.py
import pytest
def test_upper_to_lower(monkeypatch):
"""Set the USER env var to assert the behavior."""
monkeypatch.setenv("USER", "TestingUser")
assert get_os_user_lower() == "testinguser"
def test_raise_exception(monkeypatch):
"""Remove the USER env var and assert EnvironmentError is raised."""
monkeypatch.delenv("USER", raising=False)
with pytest.raises(EnvironmentError):
_ = get_os_user_lower()
This behavior can be moved into fixture
structures and shared across tests:
import pytest
@pytest.fixture
def mock_env_user(monkeypatch):
monkeypatch.setenv("USER", "TestingUser")
@pytest.fixture
def mock_env_missing(monkeypatch):
monkeypatch.delenv("USER", raising=False)
# Notice the tests reference the fixtures for mocks
def test_upper_to_lower(mock_env_user):
assert get_os_user_lower() == "testinguser"
def test_raise_exception(mock_env_missing):
with pytest.raises(EnvironmentError):
_ = get_os_user_lower()
API Reference¶
Consult the docs for the MonkeyPatch
class.