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:
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:
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:
@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
patchfunctions;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:
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.