Skip to content

#3290 improve monkeypatch #3382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a docstring here, something along the lines:

"""
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>`_.
"""

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

"""
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)