Skip to content

Commit 05f3422

Browse files
committed
Make monkeypatch invocation-scoped
1 parent 4f2bf96 commit 05f3422

File tree

4 files changed

+61
-22
lines changed

4 files changed

+61
-22
lines changed

_pytest/monkeypatch.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
1111

1212

13-
@pytest.fixture
13+
@pytest.fixture(scope='invocation')
1414
def monkeypatch(request):
1515
"""The returned ``monkeypatch`` fixture provides these
1616
helper methods to modify objects, dictionaries or os.environ::
@@ -25,9 +25,11 @@ def monkeypatch(request):
2525
monkeypatch.chdir(path)
2626
2727
All modifications will be undone after the requesting
28-
test function has finished. The ``raising``
28+
test function or fixture has finished. The ``raising``
2929
parameter determines if a KeyError or AttributeError
3030
will be raised if the set/deletion operation has no target.
31+
32+
This fixture is ``invocation``-scoped.
3133
"""
3234
mpatch = MonkeyPatch()
3335
request.addfinalizer(mpatch.undo)
@@ -97,7 +99,8 @@ def __repr__(self):
9799

98100

99101
class MonkeyPatch:
100-
""" Object keeping a record of setattr/item/env/syspath changes. """
102+
""" Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
103+
"""
101104

102105
def __init__(self):
103106
self._setattr = []

doc/en/invocation-fixture.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ Invocation-scoped fixtures
99
This feature is experimental, so if decided that it brings too much problems
1010
or considered too complicated it might be removed in pytest ``3.1``.
1111

12-
``tmpdir`` and ``monkeypatch`` might become ``invocation`` scoped
13-
fixtures in the future if decided to keep invocation-scoped fixtures.
14-
1512
Fixtures can be defined with ``invocation`` scope, meaning that the fixture
1613
can be requested by fixtures from any scope, but when they do they assume
1714
the same scope as the fixture requesting it.

doc/en/monkeypatch.rst

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ Monkeypatching/mocking modules and environments
66

77
Sometimes tests need to invoke functionality which depends
88
on global settings or which invokes code which cannot be easily
9-
tested such as network access. The ``monkeypatch`` function argument
9+
tested such as network access. The ``monkeypatch`` fixture
1010
helps you to safely set/delete an attribute, dictionary item or
1111
environment variable or to modify ``sys.path`` for importing.
1212
See the `monkeypatch blog post`_ for some introduction material
1313
and a discussion of its motivation.
1414

1515
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
1616

17+
As of pytest-3.0, the ``monkeypatch`` fixture is :ref:`invocation-scoped <invocation_scoped_fixture>`
18+
meaning it can be requested from fixtures of any scope.
19+
1720
Simple example: monkeypatching functions
1821
---------------------------------------------------
1922

@@ -53,27 +56,31 @@ This autouse fixture will be executed for each test function and it
5356
will delete the method ``request.session.Session.request``
5457
so that any attempts within tests to create http requests will fail.
5558

56-
example: setting an attribute on some class
57-
------------------------------------------------------
59+
example: setting an environment variable for the test session
60+
-------------------------------------------------------------
5861

59-
If you need to patch out ``os.getcwd()`` to return an artificial
60-
value::
62+
If you would like for an environment variable to be
63+
configured for the entire test session, you can add this to your
64+
top-level ``conftest.py`` file:
6165

62-
def test_some_interaction(monkeypatch):
63-
monkeypatch.setattr("os.getcwd", lambda: "/")
66+
.. code-block:: python
67+
68+
# content of conftest.py
69+
@pytest.fixture(scope='session', autouse=True)
70+
def enable_debugging(monkeypatch):
71+
monkeypatch.setenv("DEBUGGING_VERBOSITY", "4")
6472
65-
which is equivalent to the long form::
73+
This auto-use fixture will set the ``DEBUGGING_VERBOSITY`` environment variable for
74+
the entire test session.
6675

67-
def test_some_interaction(monkeypatch):
68-
import os
69-
monkeypatch.setattr(os, "getcwd", lambda: "/")
70-
76+
Note that the ability to use a ``monkeypatch`` fixture from a ``session``-scoped
77+
fixture was added in pytest-3.0.
7178

7279

73-
Method reference of the monkeypatch function argument
74-
-----------------------------------------------------
80+
Method reference of the monkeypatch fixture
81+
-------------------------------------------
7582

76-
.. autoclass:: monkeypatch
83+
.. autoclass:: MonkeyPatch
7784
:members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
7885

7986
``monkeypatch.setattr/delattr/delitem/delenv()`` all

testing/test_monkeypatch.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,36 @@ def test_issue1338_name_resolving():
328328
try:
329329
monkeypatch.delattr('requests.sessions.Session.request')
330330
finally:
331-
monkeypatch.undo()
331+
monkeypatch.undo()
332+
333+
334+
def test_invocation_scoped_monkeypatch(testdir):
335+
testdir.makeconftest("""
336+
import pytest
337+
import sys
338+
339+
@pytest.fixture(scope='module')
340+
def stamp_sys(monkeypatch):
341+
monkeypatch.setattr(sys, 'module_stamped', True, raising=False)
342+
""")
343+
testdir.makepyfile(test_inv_mokeypatch_1="""
344+
import sys
345+
346+
def test_stamp_1(monkeypatch, stamp_sys):
347+
assert sys.module_stamped
348+
monkeypatch.setattr(sys, 'function_stamped', True, raising=False)
349+
assert sys.function_stamped
350+
351+
def test_stamp_2(monkeypatch):
352+
assert sys.module_stamped
353+
assert not hasattr(sys, 'function_stamped')
354+
""")
355+
testdir.makepyfile(test_inv_mokeypatch_2="""
356+
import sys
357+
358+
def test_no_stamps():
359+
assert not hasattr(sys, 'module_stamped')
360+
assert not hasattr(sys, 'function_stamped')
361+
""")
362+
result = testdir.runpytest()
363+
result.stdout.fnmatch_lines(['*3 passed*'])

0 commit comments

Comments
 (0)