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 (.*)$")
@pytest.fixture
@pytest.fixture(scope='invocation')
def monkeypatch(request):
"""The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
@ -25,9 +25,11 @@ def monkeypatch(request):
monkeypatch.chdir(path)
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
will be raised if the set/deletion operation has no target.
This fixture is ``invocation``-scoped.
"""
mpatch = MonkeyPatch()
request.addfinalizer(mpatch.undo)
@ -97,7 +99,8 @@ notset = Notset()
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):
self._setattr = []

View File

@ -9,9 +9,6 @@ Invocation-scoped fixtures
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``.
``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
can be requested by fixtures from any scope, but when they do they assume
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
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
environment variable or to modify ``sys.path`` for importing.
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/
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
---------------------------------------------------
@ -53,27 +56,31 @@ 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.
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
value::
If you would like for an environment variable to be
configured for the entire test session, you can add this to your
top-level ``conftest.py`` file:
def test_some_interaction(monkeypatch):
monkeypatch.setattr("os.getcwd", lambda: "/")
.. code-block:: python
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):
import os
monkeypatch.setattr(os, "getcwd", lambda: "/")
This auto-use fixture will set the ``DEBUGGING_VERBOSITY`` environment variable for
the entire test session.
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
``monkeypatch.setattr/delattr/delitem/delenv()`` all

View File

@ -328,4 +328,36 @@ def test_issue1338_name_resolving():
try:
monkeypatch.delattr('requests.sessions.Session.request')
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*'])