Make monkeypatch invocation-scoped

This commit is contained in:
Bruno Oliveira 2016-07-20 22:05:49 -03:00
parent 4f2bf965cb
commit 05f3422d7c
4 changed files with 61 additions and 22 deletions

View File

@ -10,7 +10,7 @@ import pytest
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@pytest.fixture @pytest.fixture(scope='invocation')
def monkeypatch(request): def monkeypatch(request):
"""The returned ``monkeypatch`` fixture provides these """The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ:: helper methods to modify objects, dictionaries or os.environ::
@ -25,9 +25,11 @@ def monkeypatch(request):
monkeypatch.chdir(path) monkeypatch.chdir(path)
All modifications will be undone after the requesting All modifications will be undone after the requesting
test function has finished. The ``raising`` test function or fixture has finished. The ``raising``
parameter determines if a KeyError or AttributeError parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target. will be raised if the set/deletion operation has no target.
This fixture is ``invocation``-scoped.
""" """
mpatch = MonkeyPatch() mpatch = MonkeyPatch()
request.addfinalizer(mpatch.undo) request.addfinalizer(mpatch.undo)
@ -97,7 +99,8 @@ notset = Notset()
class MonkeyPatch: class MonkeyPatch:
""" Object keeping a record of setattr/item/env/syspath changes. """ """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
"""
def __init__(self): def __init__(self):
self._setattr = [] self._setattr = []

View File

@ -9,9 +9,6 @@ Invocation-scoped fixtures
This feature is experimental, so if decided that it brings too much problems This feature is experimental, so if decided that it brings too much problems
or considered too complicated it might be removed in pytest ``3.1``. or considered too complicated it might be removed in pytest ``3.1``.
``tmpdir`` and ``monkeypatch`` might become ``invocation`` scoped
fixtures in the future if decided to keep invocation-scoped fixtures.
Fixtures can be defined with ``invocation`` scope, meaning that the fixture Fixtures can be defined with ``invocation`` scope, meaning that the fixture
can be requested by fixtures from any scope, but when they do they assume can be requested by fixtures from any scope, but when they do they assume
the same scope as the fixture requesting it. the same scope as the fixture requesting it.

View File

@ -6,7 +6,7 @@ Monkeypatching/mocking modules and environments
Sometimes tests need to invoke functionality which depends Sometimes tests need to invoke functionality which depends
on global settings or which invokes code which cannot be easily on global settings or which invokes code which cannot be easily
tested such as network access. The ``monkeypatch`` function argument tested such as network access. The ``monkeypatch`` fixture
helps you to safely set/delete an attribute, dictionary item or helps you to safely set/delete an attribute, dictionary item or
environment variable or to modify ``sys.path`` for importing. environment variable or to modify ``sys.path`` for importing.
See the `monkeypatch blog post`_ for some introduction material See the `monkeypatch blog post`_ for some introduction material
@ -14,6 +14,9 @@ and a discussion of its motivation.
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
As of pytest-3.0, the ``monkeypatch`` fixture is :ref:`invocation-scoped <invocation_scoped_fixture>`
meaning it can be requested from fixtures of any scope.
Simple example: monkeypatching functions Simple example: monkeypatching functions
--------------------------------------------------- ---------------------------------------------------
@ -53,27 +56,31 @@ This autouse fixture will be executed for each test function and it
will delete the method ``request.session.Session.request`` will delete the method ``request.session.Session.request``
so that any attempts within tests to create http requests will fail. so that any attempts within tests to create http requests will fail.
example: setting an attribute on some class example: setting an environment variable for the test session
------------------------------------------------------ -------------------------------------------------------------
If you need to patch out ``os.getcwd()`` to return an artificial If you would like for an environment variable to be
value:: configured for the entire test session, you can add this to your
top-level ``conftest.py`` file:
def test_some_interaction(monkeypatch): .. code-block:: python
monkeypatch.setattr("os.getcwd", lambda: "/")
which is equivalent to the long form:: # content of conftest.py
@pytest.fixture(scope='session', autouse=True)
def enable_debugging(monkeypatch):
monkeypatch.setenv("DEBUGGING_VERBOSITY", "4")
def test_some_interaction(monkeypatch): This auto-use fixture will set the ``DEBUGGING_VERBOSITY`` environment variable for
import os the entire test session.
monkeypatch.setattr(os, "getcwd", lambda: "/")
Note that the ability to use a ``monkeypatch`` fixture from a ``session``-scoped
fixture was added in pytest-3.0.
Method reference of the monkeypatch function argument Method reference of the monkeypatch fixture
----------------------------------------------------- -------------------------------------------
.. autoclass:: monkeypatch .. autoclass:: MonkeyPatch
:members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo :members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
``monkeypatch.setattr/delattr/delitem/delenv()`` all ``monkeypatch.setattr/delattr/delitem/delenv()`` all

View File

@ -328,4 +328,36 @@ def test_issue1338_name_resolving():
try: try:
monkeypatch.delattr('requests.sessions.Session.request') monkeypatch.delattr('requests.sessions.Session.request')
finally: finally:
monkeypatch.undo() monkeypatch.undo()
def test_invocation_scoped_monkeypatch(testdir):
testdir.makeconftest("""
import pytest
import sys
@pytest.fixture(scope='module')
def stamp_sys(monkeypatch):
monkeypatch.setattr(sys, 'module_stamped', True, raising=False)
""")
testdir.makepyfile(test_inv_mokeypatch_1="""
import sys
def test_stamp_1(monkeypatch, stamp_sys):
assert sys.module_stamped
monkeypatch.setattr(sys, 'function_stamped', True, raising=False)
assert sys.function_stamped
def test_stamp_2(monkeypatch):
assert sys.module_stamped
assert not hasattr(sys, 'function_stamped')
""")
testdir.makepyfile(test_inv_mokeypatch_2="""
import sys
def test_no_stamps():
assert not hasattr(sys, 'module_stamped')
assert not hasattr(sys, 'function_stamped')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['*3 passed*'])