219 lines
6.7 KiB
ReStructuredText
219 lines
6.7 KiB
ReStructuredText
===========
|
|
pytest-mock
|
|
===========
|
|
|
|
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
|
|
provided by the excellent `mock <http://pypi.python.org/pypi/mock>`_ package,
|
|
but with the benefit of not having to worry about undoing patches at the end
|
|
of a test:
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
def test_unix_fs(mocker):
|
|
mocker.patch('os.remove')
|
|
UnixFS.rm('file')
|
|
os.remove.assert_called_once_with('file')
|
|
|
|
|
|
.. Using PNG badges because PyPI doesn't support SVG
|
|
|
|
|python| |version| |downloads| |ci| |coverage|
|
|
|
|
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png
|
|
:target: https://pypi.python.org/pypi/pytest-mock
|
|
|
|
.. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png
|
|
:target: https://pypi.python.org/pypi/pytest-mock
|
|
|
|
.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png
|
|
:target: https://travis-ci.org/pytest-dev/pytest-mock
|
|
|
|
.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png
|
|
:target: https://coveralls.io/r/pytest-dev/pytest-mock
|
|
|
|
.. |python| image:: https://pypip.in/py_versions/pytest-mock/badge.png
|
|
:target: https://pypi.python.org/pypi/pytest-mock/
|
|
:alt: Supported Python versions
|
|
|
|
|
|
Usage
|
|
=====
|
|
|
|
The ``mocker`` fixture has the same API as
|
|
`mock.patch <http://www.voidspace.org.uk/python/mock/patch.html#patch-decorators>`_,
|
|
supporting the same arguments:
|
|
|
|
.. code-block:: python
|
|
|
|
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 at this point.
|
|
|
|
Note that, although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. 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:
|
|
|
|
.. code-block:: python
|
|
|
|
def test_context_manager(mocker):
|
|
a = A()
|
|
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
|
assert a.doIt() == True
|
|
|
|
.. code-block:: console
|
|
|
|
================================== FAILURES ===================================
|
|
____________________________ test_context_manager _____________________________
|
|
in test_context_manager
|
|
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
|
E AttributeError: __exit__
|
|
|
|
|
|
You can also access ``Mock`` and ``MagicMock`` directly using from ``mocker``
|
|
fixture:
|
|
|
|
.. code-block:: python
|
|
|
|
def test_feature(mocker):
|
|
ret = [mocker.Mock(return_value=True), mocker.Mock(return_value=True)]
|
|
mocker.patch('mylib.func', side_effect=ret)
|
|
|
|
*New in version 0.5*
|
|
|
|
Spy
|
|
---
|
|
|
|
*New in version 0.6*
|
|
|
|
The spy acts exactly like the original method in all cases, except it allows use of `mock`
|
|
features with it, like retrieving call count.
|
|
|
|
From version 0.7 onward it also works for class and static methods. Originally it was only safe to
|
|
use with instance methods.
|
|
|
|
.. code-block:: python
|
|
|
|
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
|
|
----
|
|
|
|
*New in version 0.6*
|
|
|
|
The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance.
|
|
|
|
.. code-block:: python
|
|
|
|
def test_stub(mocker):
|
|
def foo(on_something):
|
|
on_something('foo', 'bar')
|
|
|
|
stub = mocker.stub()
|
|
|
|
foo(stub)
|
|
stub.assert_called_once_with('foo', 'bar')
|
|
|
|
Note
|
|
----
|
|
|
|
Prior to version ``0.4.0``, the ``mocker`` fixture was named ``mock``.
|
|
This was changed because naming the fixture ``mock`` conflicts with the
|
|
actual ``mock`` module, which made using it awkward when access to both the
|
|
module and the plugin were required within a test.
|
|
|
|
The old fixture ``mock`` still works, but its use is discouraged and will be
|
|
removed in version ``1.0``.
|
|
|
|
Requirements
|
|
============
|
|
|
|
* Python 2.6+, Python 3.2+
|
|
* pytest
|
|
* mock (for Python < 3.3)
|
|
|
|
|
|
Install
|
|
=======
|
|
|
|
Install using `pip <http://pip-installer.org/>`_:
|
|
|
|
.. code-block:: console
|
|
|
|
$ pip install pytest-mock
|
|
|
|
Changelog
|
|
=========
|
|
|
|
Please consult `releases <https://github.com/pytest-dev/pytest-mock/releases>`_.
|
|
|
|
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:
|
|
|
|
.. code-block:: python
|
|
|
|
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:
|
|
|
|
.. code-block:: python
|
|
|
|
@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;
|