Skip to content

Commit

Permalink
Add contextlib.ExitStack() alternative (pytest-dev#153)
Browse files Browse the repository at this point in the history
* Also move context-manager warning to the top
  • Loading branch information
graingert authored and nicoddemus committed Aug 20, 2019
1 parent 189cc59 commit 58b5048
Showing 1 changed file with 33 additions and 34 deletions.
67 changes: 33 additions & 34 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@ This will force the plugin to import ``mock`` instead of the ``unittest.mock`` m
Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option
to use the ``mock`` package from PyPI anyway.

Note about usage as context manager
-----------------------------------

Although mocker's API is intentionally the same as ``mock.patch``'s, its use
as context manager and function decorator is **not** supported through the
fixture:

.. code-block:: python
def test_context_manager(mocker):
a = A()
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS
assert a.doIt() == True
The purpose of this plugin is to make the use of context managers and
function decorators for mocking unnecessary.


Requirements
============
Expand Down Expand Up @@ -277,47 +294,29 @@ But this poses a few disadvantages:
naming fixtures as parameters, or ``pytest.mark.parametrize``;
- you can't easily undo the mocking during the test execution;


**Note about usage as context manager**

Although mocker's API is intentionally the same as ``mock.patch``'s, its use
as context manager and function decorator is **not** supported through the
fixture. The purpose of this plugin is to make the use of context managers and
function decorators for mocking unnecessary. Indeed, trying to use the
functionality in ``mocker`` in this manner can lead to non-intuitive errors:
An alternative is to use ``contextlib.ExitStack`` to stack the context managers in a single level of indentation
to improve the flow of the test:

.. code-block:: python
def test_context_manager(mocker):
a = A()
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
assert a.doIt() == True
.. code-block:: console
================================== FAILURES ===================================
____________________________ test_context_manager _____________________________
in test_context_manager
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
E AttributeError: __exit__
You can however use ``mocker.mock_module`` to access the underlying ``mock``
module, e.g. to return a context manager in a fixture that mocks something
temporarily:
import contextlib
import mock
.. code-block:: python
def test_unix_fs():
with contextlib.ExitStack() as stack:
stack.enter_context(mock.patch('os.remove'))
UnixFS.rm('file')
os.remove.assert_called_once_with('file')
@pytest.fixture
def fixture_cm(mocker):
@contextlib.contextmanager
def my_cm():
def mocked():
pass
stack.enter_context(mock.patch('os.listdir'))
assert UnixFS.ls('dir') == expected
# ...
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
yield
return my_cm
stack.enter_context(mock.patch('shutil.copy'))
UnixFS.cp('src', 'dst')
# ...
But this is arguably a little more complex than using ``pytest-mock``.

Contributing
============
Expand Down

0 comments on commit 58b5048

Please sign in to comment.