103 lines
2.8 KiB
ReStructuredText
103 lines
2.8 KiB
ReStructuredText
|
=======
|
||
|
Remarks
|
||
|
=======
|
||
|
|
||
|
Type annotations
|
||
|
----------------
|
||
|
|
||
|
``pytest-mock`` is fully type annotated, letting users use static type checkers to
|
||
|
test their code.
|
||
|
|
||
|
The ``mocker`` fixture returns ``pytest_mock.MockerFixture`` which can be used
|
||
|
to annotate test functions:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
from pytest_mock import MockerFixture
|
||
|
|
||
|
def test_foo(mocker: MockerFixture) -> None:
|
||
|
...
|
||
|
|
||
|
The type annotations have been checked with ``mypy``, which is the only
|
||
|
type checker supported at the moment; other type-checkers might work
|
||
|
but are not currently tested.
|
||
|
|
||
|
|
||
|
Why bother with a plugin?
|
||
|
=========================
|
||
|
|
||
|
There are a number of different ``patch`` usages in the standard ``mock`` API,
|
||
|
but IMHO they don't scale very well when you have more than one or two
|
||
|
patches to apply.
|
||
|
|
||
|
It may lead to an excessive nesting of ``with`` statements, breaking the flow
|
||
|
of the test:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
import mock
|
||
|
|
||
|
def test_unix_fs():
|
||
|
with mock.patch('os.remove'):
|
||
|
UnixFS.rm('file')
|
||
|
os.remove.assert_called_once_with('file')
|
||
|
|
||
|
with mock.patch('os.listdir'):
|
||
|
assert UnixFS.ls('dir') == expected
|
||
|
# ...
|
||
|
|
||
|
with mock.patch('shutil.copy'):
|
||
|
UnixFS.cp('src', 'dst')
|
||
|
# ...
|
||
|
|
||
|
|
||
|
One can use ``patch`` as a decorator to improve the flow of the test:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
@mock.patch('os.remove')
|
||
|
@mock.patch('os.listdir')
|
||
|
@mock.patch('shutil.copy')
|
||
|
def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove):
|
||
|
UnixFS.rm('file')
|
||
|
os.remove.assert_called_once_with('file')
|
||
|
|
||
|
assert UnixFS.ls('dir') == expected
|
||
|
# ...
|
||
|
|
||
|
UnixFS.cp('src', 'dst')
|
||
|
# ...
|
||
|
|
||
|
But this poses a few disadvantages:
|
||
|
|
||
|
- test functions must receive the mock objects as parameter, even if you don't plan to
|
||
|
access them directly; also, order depends on the order of the decorated ``patch``
|
||
|
functions;
|
||
|
- receiving the mocks as parameters doesn't mix nicely with pytest's approach of
|
||
|
naming fixtures as parameters, or ``pytest.mark.parametrize``;
|
||
|
- you can't easily undo the mocking during the test execution;
|
||
|
|
||
|
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
|
||
|
|
||
|
import contextlib
|
||
|
import mock
|
||
|
|
||
|
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')
|
||
|
|
||
|
stack.enter_context(mock.patch('os.listdir'))
|
||
|
assert UnixFS.ls('dir') == expected
|
||
|
# ...
|
||
|
|
||
|
stack.enter_context(mock.patch('shutil.copy'))
|
||
|
UnixFS.cp('src', 'dst')
|
||
|
# ...
|
||
|
|
||
|
But this is arguably a little more complex than using ``pytest-mock``.
|