Merge remote-tracking branch 'upstream/features' into features

This commit is contained in:
turturica 2018-04-20 15:21:24 -07:00
commit e44d4e6508
4 changed files with 55 additions and 0 deletions

View File

@ -4,6 +4,8 @@ from __future__ import absolute_import, division, print_function
import os import os
import sys import sys
import re import re
from contextlib import contextmanager
import six import six
from _pytest.fixtures import fixture from _pytest.fixtures import fixture
@ -106,6 +108,29 @@ class MonkeyPatch(object):
self._cwd = None self._cwd = None
self._savesyspath = None self._savesyspath = None
@contextmanager
def context(self):
"""
Context manager that returns a new :class:`MonkeyPatch` object which
undoes any patching done inside the ``with`` block upon exit:
.. code-block:: python
import functools
def test_partial(monkeypatch):
with monkeypatch.context() as m:
m.setattr(functools, "partial", 3)
Useful in situations where it is desired to undo some patches before the test ends,
such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_.
"""
m = MonkeyPatch()
try:
yield m
finally:
m.undo()
def setattr(self, target, name, value=notset, raising=True): def setattr(self, target, name, value=notset, raising=True):
""" Set attribute value on target, memorizing the old value. """ Set attribute value on target, memorizing the old value.
By default raise AttributeError if the attribute did not exist. By default raise AttributeError if the attribute did not exist.

2
changelog/3290.feature Normal file
View File

@ -0,0 +1,2 @@
``monkeypatch`` now supports a ``context()`` function which acts as a context manager which undoes all patching done
within the ``with`` block.

View File

@ -61,6 +61,22 @@ so that any attempts within tests to create http requests will fail.
``compile``, etc., because it might break pytest's internals. If that's ``compile``, etc., because it might break pytest's internals. If that's
unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might
help although there's no guarantee. 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
:meth:`MonkeyPatch.context` to limit the patching to the block you want tested:
.. code-block:: python
import functools
def test_partial(monkeypatch):
with monkeypatch.context() as m:
m.setattr(functools, "partial", 3)
assert functools.partial == 3
See issue `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_ for details.
.. currentmodule:: _pytest.monkeypatch .. currentmodule:: _pytest.monkeypatch

View File

@ -327,3 +327,15 @@ def test_issue1338_name_resolving():
monkeypatch.delattr('requests.sessions.Session.request') monkeypatch.delattr('requests.sessions.Session.request')
finally: finally:
monkeypatch.undo() monkeypatch.undo()
def test_context():
monkeypatch = MonkeyPatch()
import functools
import inspect
with monkeypatch.context() as m:
m.setattr(functools, "partial", 3)
assert not inspect.isclass(functools.partial)
assert inspect.isclass(functools.partial)