1=========== 2pytest-mock 3=========== 4 5This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API 6provided by the `mock package <http://pypi.python.org/pypi/mock>`_, 7but with the benefit of not having to worry about undoing patches at the end 8of a test: 9 10.. code-block:: python 11 12 import os 13 14 class UnixFS: 15 16 @staticmethod 17 def rm(filename): 18 os.remove(filename) 19 20 def test_unix_fs(mocker): 21 mocker.patch('os.remove') 22 UnixFS.rm('file') 23 os.remove.assert_called_once_with('file') 24 25 26 27|python| |version| |anaconda| |ci| |appveyor| |coverage| |black| 28 29.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg 30 :target: https://pypi.python.org/pypi/pytest-mock 31 32.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg 33 :target: https://anaconda.org/conda-forge/pytest-mock 34 35.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.svg 36 :target: https://travis-ci.org/pytest-dev/pytest-mock 37 38.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true 39 :target: https://ci.appveyor.com/project/pytestbot/pytest-mock 40 41.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.svg 42 :target: https://coveralls.io/r/pytest-dev/pytest-mock 43 44.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg 45 :target: https://pypi.python.org/pypi/pytest-mock/ 46 47.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg 48 :target: https://github.com/ambv/black 49 50`Professionally supported pytest-mock is now available <https://tidelift.com/subscription/pkg/pypi-pytest_mock?utm_source=pypi-pytest-mock&utm_medium=referral&utm_campaign=readme>`_ 51 52Usage 53===== 54 55The ``mocker`` fixture has the same API as 56`mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_, 57supporting the same arguments: 58 59.. code-block:: python 60 61 def test_foo(mocker): 62 # all valid calls 63 mocker.patch('os.remove') 64 mocker.patch.object(os, 'listdir', autospec=True) 65 mocked_isfile = mocker.patch('os.path.isfile') 66 67The supported methods are: 68 69* `mocker.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_ 70* `mocker.patch.object <https://docs.python.org/3/library/unittest.mock.html#patch-object>`_ 71* `mocker.patch.multiple <https://docs.python.org/3/library/unittest.mock.html#patch-multiple>`_ 72* `mocker.patch.dict <https://docs.python.org/3/library/unittest.mock.html#patch-dict>`_ 73* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_ 74* ``mocker.resetall()``: calls `reset_mock() <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.reset_mock>`_ in all mocked objects up to this point. 75 76These objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: 77 78* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_ 79* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_ 80* `PropertyMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.PropertyMock>`_ 81* `ANY <https://docs.python.org/3/library/unittest.mock.html#any>`_ 82* `DEFAULT <https://docs.python.org/3/library/unittest.mock.html#default>`_ *(Version 1.4)* 83* `call <https://docs.python.org/3/library/unittest.mock.html#call>`_ *(Version 1.1)* 84* `sentinel <https://docs.python.org/3/library/unittest.mock.html#sentinel>`_ *(Version 1.2)* 85* `mock_open <https://docs.python.org/3/library/unittest.mock.html#mock-open>`_ 86 87 88Spy 89--- 90 91The spy acts exactly like the original method in all cases, except it allows use of `mock` 92features with it, like retrieving call count. It also works for class and static methods. 93 94 95.. code-block:: python 96 97 def test_spy(mocker): 98 class Foo(object): 99 def bar(self): 100 return 42 101 102 foo = Foo() 103 mocker.spy(foo, 'bar') 104 assert foo.bar() == 42 105 assert foo.bar.call_count == 1 106 107Stub 108---- 109 110 111The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. 112May be passed a name to be used by the constructed stub object in its repr (useful for debugging). 113 114.. code-block:: python 115 116 def test_stub(mocker): 117 def foo(on_something): 118 on_something('foo', 'bar') 119 120 stub = mocker.stub(name='on_something_stub') 121 122 foo(stub) 123 stub.assert_called_once_with('foo', 'bar') 124 125 126Improved reporting of mock call assertion errors 127------------------------------------------------ 128 129 130This plugin monkeypatches the mock library to improve pytest output for failures 131of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback 132entries from the ``mock`` module. 133 134It also adds introspection information on differing call arguments when 135calling the helper methods. This features catches `AssertionError` raised in 136the method, and uses py.test's own `advanced assertions`_ to return a better 137diff:: 138 139 140 mocker = <pytest_mock.MockFixture object at 0x0381E2D0> 141 142 def test(mocker): 143 m = mocker.Mock() 144 m('fo') 145 > m.assert_called_once_with('', bar=4) 146 E AssertionError: Expected call: mock('', bar=4) 147 E Actual call: mock('fo') 148 E 149 E pytest introspection follows: 150 E 151 E Args: 152 E assert ('fo',) == ('',) 153 E At index 0 diff: 'fo' != '' 154 E Use -v to get the full diff 155 E Kwargs: 156 E assert {} == {'bar': 4} 157 E Right contains more items: 158 E {'bar': 4} 159 E Use -v to get the full diff 160 161 162 test_foo.py:6: AssertionError 163 ========================== 1 failed in 0.03 seconds =========================== 164 165 166This is useful when asserting mock calls with many/nested arguments and trying 167to quickly see the difference. 168 169This feature is probably safe, but if you encounter any problems it can be disabled in 170your ``pytest.ini`` file: 171 172.. code-block:: ini 173 174 [pytest] 175 mock_traceback_monkeypatch = false 176 177Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying 178mechanism used to suppress traceback entries from ``mock`` module does not work with that option 179anyway plus it generates confusing messages on Python 3.5 due to exception chaining 180 181.. _advanced assertions: http://pytest.org/latest/assert.html 182 183 184Use standalone "mock" package 185----------------------------- 186 187*New in version 1.4.0.* 188 189Python 3 users might want to use a newest version of the ``mock`` package as published on PyPI 190than the one that comes with the Python distribution. 191 192.. code-block:: ini 193 194 [pytest] 195 mock_use_standalone_module = true 196 197This will force the plugin to import ``mock`` instead of the ``unittest.mock`` module bundled with 198Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option 199to use the ``mock`` package from PyPI anyway. 200 201 202Requirements 203============ 204 205* Python 2.7, Python 3.4+ 206* pytest 207* mock (for Python 2) 208 209 210Install 211======= 212 213Install using `pip <http://pip-installer.org/>`_: 214 215.. code-block:: console 216 217 $ pip install pytest-mock 218 219Changelog 220========= 221 222Please consult the `changelog page`_. 223 224.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst 225 226Why bother with a plugin? 227========================= 228 229There are a number of different ``patch`` usages in the standard ``mock`` API, 230but IMHO they don't scale very well when you have more than one or two 231patches to apply. 232 233It may lead to an excessive nesting of ``with`` statements, breaking the flow 234of the test: 235 236.. code-block:: python 237 238 import mock 239 240 def test_unix_fs(): 241 with mock.patch('os.remove'): 242 UnixFS.rm('file') 243 os.remove.assert_called_once_with('file') 244 245 with mock.patch('os.listdir'): 246 assert UnixFS.ls('dir') == expected 247 # ... 248 249 with mock.patch('shutil.copy'): 250 UnixFS.cp('src', 'dst') 251 # ... 252 253 254One can use ``patch`` as a decorator to improve the flow of the test: 255 256.. code-block:: python 257 258 @mock.patch('os.remove') 259 @mock.patch('os.listdir') 260 @mock.patch('shutil.copy') 261 def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): 262 UnixFS.rm('file') 263 os.remove.assert_called_once_with('file') 264 265 assert UnixFS.ls('dir') == expected 266 # ... 267 268 UnixFS.cp('src', 'dst') 269 # ... 270 271But this poses a few disadvantages: 272 273- test functions must receive the mock objects as parameter, even if you don't plan to 274 access them directly; also, order depends on the order of the decorated ``patch`` 275 functions; 276- receiving the mocks as parameters doesn't mix nicely with pytest's approach of 277 naming fixtures as parameters, or ``pytest.mark.parametrize``; 278- you can't easily undo the mocking during the test execution; 279 280 281**Note about usage as context manager** 282 283Although mocker's API is intentionally the same as ``mock.patch``'s, its use 284as context manager and function decorator is **not** supported through the 285fixture. The purpose of this plugin is to make the use of context managers and 286function decorators for mocking unnecessary. Indeed, trying to use the 287functionality in ``mocker`` in this manner can lead to non-intuitive errors: 288 289.. code-block:: python 290 291 def test_context_manager(mocker): 292 a = A() 293 with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): 294 assert a.doIt() == True 295 296.. code-block:: console 297 298 ================================== FAILURES =================================== 299 ____________________________ test_context_manager _____________________________ 300 in test_context_manager 301 with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): 302 E AttributeError: __exit__ 303 304You can however use ``mocker.mock_module`` to access the underlying ``mock`` 305module, e.g. to return a context manager in a fixture that mocks something 306temporarily: 307 308.. code-block:: python 309 310 @pytest.fixture 311 def fixture_cm(mocker): 312 @contextlib.contextmanager 313 def my_cm(): 314 def mocked(): 315 pass 316 317 with mocker.mock_module.patch.object(SomeClass, 'method', mocked): 318 yield 319 return my_cm 320 321 322Contributing 323============ 324 325Contributions are welcome! After cloning the repository, create a virtual env 326and install ``pytest-mock`` in editable mode with ``dev`` extras: 327 328.. code-block:: console 329 330 $ pip install --editable .[dev] 331 $ pre-commit install 332 333Tests are run with ``tox``, you can run the baseline environments before submitting a PR: 334 335.. code-block:: console 336 337 $ tox -e py27,py36,linting 338 339Style checks and formatting are done automatically during commit courtesy of 340`pre-commit <https://pre-commit.com>`_. 341 342License 343======= 344 345Distributed under the terms of the `MIT`_ license. 346 347.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE 348