Skip to content

Commit

Permalink
Merge pull request #3382 from feuillemorte/3290-improve-monkeypatch
Browse files Browse the repository at this point in the history
#3290 improve monkeypatch
  • Loading branch information
nicoddemus committed Apr 19, 2018
2 parents 13a6f63 + 283ac8b commit 3318e53
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 0 deletions.
25 changes: 25 additions & 0 deletions _pytest/monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import os
import sys
import re
from contextlib import contextmanager

import six
from _pytest.fixtures import fixture

Expand Down Expand Up @@ -106,6 +108,29 @@ def __init__(self):
self._cwd = 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):
""" Set attribute value on target, memorizing the old value.
By default raise AttributeError if the attribute did not exist.
Expand Down
2 changes: 2 additions & 0 deletions changelog/3290.feature
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions doc/en/monkeypatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
: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
Expand Down
12 changes: 12 additions & 0 deletions testing/test_monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,15 @@ def test_issue1338_name_resolving():
monkeypatch.delattr('requests.sessions.Session.request')
finally:
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)

0 comments on commit 3318e53

Please sign in to comment.