pytest-mock
This plugin installs a mocker
fixture which is a thin-wrapper around the patching API
provided by the mock package,
but with the benefit of not having to worry about undoing patches at the end
of a test:
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')
If you found this library useful, donate some CPU cycles to its development efforts by clicking above. Thank you! 😇
Usage
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
: see http://www.voidspace.org.uk/python/mock/patch.html#patch.mocker.patch.object
: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object.mocker.patch.multiple
: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple.mocker.patch.dict
: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict.mocker.stopall()
: stops all active patches up to this point.mocker.resetall()
: callsreset_mock()
in all mocked objects up to this point.
Some objects from the mock
module are accessible directly from mocker
for convenience:
- Mock
- MagicMock
- PropertyMock
- ANY
- DEFAULT (Version 1.4)
- call (Version 1.1)
- sentinel (Version 1.2)
- mock_open
Spy
The spy acts exactly like the original method in all cases, except it allows use of mock features with it, like retrieving call count. It also works for class and static methods.
def test_spy(mocker):
class Foo(object):
def bar(self):
return 42
foo = Foo()
mocker.spy(foo, 'bar')
assert foo.bar() == 42
assert foo.bar.call_count == 1
Stub
The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. May be passed a name to be used by the constructed stub object 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')
Improved reporting of mock call assertion errors
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 py.test's own advanced assertions to return a better diff:
mocker = <pytest_mock.MockFixture 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
Use standalone "mock" package
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.
Requirements
- Python 2.7, Python 3.4+
- pytest
- mock (for Python 2)
Install
Install using pip:
$ pip install pytest-mock
Changelog
Please consult the changelog page.
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;
Note about usage as context manager
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. The purpose of this plugin is to make the use of context managers and
function decorators for mocking unnecessary. Indeed, trying to use the
functionality in mocker
in this manner can lead to non-intuitive errors:
def test_context_manager(mocker):
a = A()
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
assert a.doIt() == True
================================== FAILURES ===================================
____________________________ test_context_manager _____________________________
in test_context_manager
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
E AttributeError: __exit__
You can however use mocker.mock_module
to access the underlying mock
module, e.g. to return a context manager in a fixture that mocks something
temporarily:
@pytest.fixture
def fixture_cm(mocker):
@contextlib.contextmanager
def my_cm():
def mocked():
pass
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
yield
return my_cm
License
Distributed under the terms of the MIT license.