Skip to content

Commit 58b5048

Browse files
graingertnicoddemus
authored andcommitted
Add contextlib.ExitStack() alternative (#153)
* Also move context-manager warning to the top
1 parent 189cc59 commit 58b5048

File tree

1 file changed

+33
-34
lines changed

1 file changed

+33
-34
lines changed

README.rst

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,23 @@ This will force the plugin to import ``mock`` instead of the ``unittest.mock`` m
198198
Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option
199199
to use the ``mock`` package from PyPI anyway.
200200

201+
Note about usage as context manager
202+
-----------------------------------
203+
204+
Although mocker's API is intentionally the same as ``mock.patch``'s, its use
205+
as context manager and function decorator is **not** supported through the
206+
fixture:
207+
208+
.. code-block:: python
209+
210+
def test_context_manager(mocker):
211+
a = A()
212+
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS
213+
assert a.doIt() == True
214+
215+
The purpose of this plugin is to make the use of context managers and
216+
function decorators for mocking unnecessary.
217+
201218

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

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

289300
.. code-block:: python
290301
291-
def test_context_manager(mocker):
292-
a = A()
293-
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
294-
assert a.doIt() == True
295-
296-
.. code-block:: console
297-
298-
================================== FAILURES ===================================
299-
____________________________ test_context_manager _____________________________
300-
in test_context_manager
301-
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
302-
E AttributeError: __exit__
303-
304-
You can however use ``mocker.mock_module`` to access the underlying ``mock``
305-
module, e.g. to return a context manager in a fixture that mocks something
306-
temporarily:
302+
import contextlib
303+
import mock
307304
308-
.. code-block:: python
305+
def test_unix_fs():
306+
with contextlib.ExitStack() as stack:
307+
stack.enter_context(mock.patch('os.remove'))
308+
UnixFS.rm('file')
309+
os.remove.assert_called_once_with('file')
309310
310-
@pytest.fixture
311-
def fixture_cm(mocker):
312-
@contextlib.contextmanager
313-
def my_cm():
314-
def mocked():
315-
pass
311+
stack.enter_context(mock.patch('os.listdir'))
312+
assert UnixFS.ls('dir') == expected
313+
# ...
316314
317-
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
318-
yield
319-
return my_cm
315+
stack.enter_context(mock.patch('shutil.copy'))
316+
UnixFS.cp('src', 'dst')
317+
# ...
320318
319+
But this is arguably a little more complex than using ``pytest-mock``.
321320

322321
Contributing
323322
============

0 commit comments

Comments
 (0)