Skip to content
Back to top
View this page

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