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