This plugin provides a mocker
fixture which is a thin-wrapper around the patching API
provided by the mock package:
import os
class UnixFS:
@staticmethod
def rm(filename):
os.remove(filename)
def test_unix_fs(mocker):
mocker.patch('os.remove')
UnixFS.rm('file')
os.remove.assert_called_once_with('file')
Besides undoing the mocking automatically after the end of the test, it also provides other
nice utilities such as spy
and stub
, and uses pytest introspection when
comparing calls.
Professionally supported pytest-mock is now available
The mocker
fixture has the same API as
mock.patch,
supporting the same arguments:
def test_foo(mocker):
# all valid calls
mocker.patch('os.remove')
mocker.patch.object(os, 'listdir', autospec=True)
mocked_isfile = mocker.patch('os.path.isfile')
The supported methods are:
- mocker.patch
- mocker.patch.object
- mocker.patch.multiple
- mocker.patch.dict
- mocker.stopall
mocker.resetall()
: calls reset_mock() in all mocked objects up to this point.
Also, as a convenience, these names from the mock
module are accessible directly from mocker
:
- Mock
- MagicMock
- PropertyMock
- ANY
- DEFAULT (Version 1.4)
- call (Version 1.1)
- sentinel (Version 1.2)
- mock_open
- seal (Version 3.4)
It is also possible to use mocking functionality from fixtures of other scopes using the appropriate mock fixture:
class_mocker
module_mocker
package_mocker
session_mocker
New in version 3.3.0.
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.
The mocker.spy
object acts exactly like the original method in all cases, except the spy
also tracks function/method calls, return values and exceptions raised.
def test_spy_method(mocker):
class Foo(object):
def bar(self, v):
return v * 2
foo = Foo()
spy = mocker.spy(foo, 'bar')
assert foo.bar(21) == 42
spy.assert_called_once_with(21)
assert spy.spy_return == 42
def test_spy_function(mocker):
# mymodule declares `myfunction` which just returns 42
import mymodule
spy = mocker.spy(mymodule, "myfunction")
assert mymodule.myfunction() == 42
assert spy.call_count == 1
assert spy.spy_return == 42
The object returned by mocker.spy
is a MagicMock
object, so all standard checking functions
are available (like assert_called_once_with
or call_count
in the examples above).
In addition, spy objects contain two extra attributes:
spy_return
: contains the returned value of the spied function.spy_exception
: contain the last exception value raised by the spied function/method when it was last called, orNone
if no exception was raised.
Besides functions and normal methods, mocker.spy
also works for class and static methods.
As of version 3.0.0, mocker.spy
also works with async def
functions.
Note
In versions earlier than 2.0
, the attributes were called return_value
and
side_effect
respectively, but due to incompatibilities with unittest.mock
they had to be renamed (see #175 for details).
The stub is a mock object that accepts any arguments and is useful to test callbacks.
It may receive an optional name that is shown in its repr
, useful for debugging.
def test_stub(mocker):
def foo(on_something):
on_something('foo', 'bar')
stub = mocker.stub(name='on_something_stub')
foo(stub)
stub.assert_called_once_with('foo', 'bar')
This plugin monkeypatches the mock library to improve pytest output for failures
of mock call assertions like Mock.assert_called_with()
by hiding internal traceback
entries from the mock
module.
It also adds introspection information on differing call arguments when calling the helper methods. This features catches AssertionError raised in the method, and uses pytest's own advanced assertions to return a better diff:
mocker = <pytest_mock.MockerFixture object at 0x0381E2D0> def test(mocker): m = mocker.Mock() m('fo') > m.assert_called_once_with('', bar=4) E AssertionError: Expected call: mock('', bar=4) E Actual call: mock('fo') E E pytest introspection follows: E E Args: E assert ('fo',) == ('',) E At index 0 diff: 'fo' != '' E Use -v to get the full diff E Kwargs: E assert {} == {'bar': 4} E Right contains more items: E {'bar': 4} E Use -v to get the full diff test_foo.py:6: AssertionError ========================== 1 failed in 0.03 seconds ===========================
This is useful when asserting mock calls with many/nested arguments and trying to quickly see the difference.
This feature is probably safe, but if you encounter any problems it can be disabled in
your pytest.ini
file:
[pytest]
mock_traceback_monkeypatch = false
Note that this feature is automatically disabled with the --tb=native
option. The underlying
mechanism used to suppress traceback entries from mock
module does not work with that option
anyway plus it generates confusing messages on Python 3.5 due to exception chaining
New in version 1.4.0.
Python 3 users might want to use a newest version of the mock
package as published on PyPI
than the one that comes with the Python distribution.
[pytest]
mock_use_standalone_module = true
This will force the plugin to import mock
instead of the unittest.mock
module bundled with
Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option
to use the mock
package from PyPI anyway.
Although mocker's API is intentionally the same as mock.patch
's, its use
as context manager and function decorator is not supported through the
fixture:
def test_context_manager(mocker):
a = A()
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS
assert a.doIt() == True
The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary, so it will emit a warning when used as such.
If you really intend to mock a context manager, mocker.patch.context_manager
exists
which won't issue the above warning.
Install using pip:
$ pip install pytest-mock
Please consult the changelog page.
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
.
Contributions are welcome! After cloning the repository, create a virtual env
and install pytest-mock
in editable mode with dev
extras:
$ pip install --editable .[dev]
$ pre-commit install
Tests are run with tox
, you can run the baseline environments before submitting a PR:
$ tox -e py38,linting
Style checks and formatting are done automatically during commit courtesy of pre-commit.
Distributed under the terms of the MIT license.
To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.