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