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.