New upstream version 3.8.2

This commit is contained in:
Julian Gilbey 2022-07-15 07:47:42 +01:00
parent 6ed0752e75
commit b4297c2616
23 changed files with 658 additions and 1132 deletions

39
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,39 @@
name: deploy
on:
push:
tags:
- "v*"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.7"
- name: Install wheel
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: |
python -m build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_token }}
- name: Generate release notes
run: |
pip install pypandoc
sudo apt-get install pandoc
python scripts/gen-release-notes.py
- name: GitHub Release
uses: softprops/action-gh-release@v1
with:
body_path: scripts/latest-release-notes.md

View file

@ -1,72 +0,0 @@
name: build
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python: ["3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-latest, windows-latest]
include:
- python: "3.7"
tox_env: "py37"
- python: "3.8"
tox_env: "py38"
- python: "3.9"
tox_env: "py39"
- python: "3.10"
tox_env: "py310"
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Test
run: |
tox -e ${{ matrix.tox_env }}
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.7"
- name: Install wheel
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: |
python -m build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_token }}
- name: Generate release notes
run: |
pip install pypandoc
python scripts/gen-release-notes.py
- name: GitHub Release
uses: softprops/action-gh-release@v1
with:
body_path: scripts/latest-release-notes.md

38
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python: ["3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-latest, windows-latest]
include:
- python: "3.7"
tox_env: "py37"
- python: "3.8"
tox_env: "py38"
- python: "3.9"
tox_env: "py39"
- python: "3.10"
tox_env: "py310"
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Test
run: |
tox -e ${{ matrix.tox_env }}

View file

@ -1,12 +1,12 @@
exclude: '^($|.*\.bin)' exclude: '^($|.*\.bin)'
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.12b0 rev: 22.6.0
hooks: hooks:
- id: black - id: black
args: [--safe, --quiet] args: [--safe, --quiet]
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0 rev: v4.3.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@ -19,12 +19,12 @@ repos:
language: python language: python
additional_dependencies: [pygments, restructuredtext_lint] additional_dependencies: [pygments, restructuredtext_lint]
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
rev: v2.6.0 rev: v3.3.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: ['--application-directories=.:src'] args: ['--application-directories=.:src']
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931 # NOTE: keep this in sync with tox.ini rev: v0.961 # NOTE: keep this in sync with tox.ini
hooks: hooks:
- id: mypy - id: mypy
files: ^(src|tests) files: ^(src|tests)

View file

@ -1,3 +1,28 @@
Releases
========
3.8.2 (2022-07-05)
------------------
- Fixed `AsyncMock` support for Python 3.7+ in `mocker.async_stub` (`#302`_).
.. _#302: https://github.com/pytest-dev/pytest-mock/pull/302
3.8.1 (2022-06-24)
------------------
* Fixed regression caused by an explicit ``mock`` dependency in the code (`#298`_).
.. _#298: https://github.com/pytest-dev/pytest-mock/issues/298
3.8.0 (2022-06-24)
------------------
* Add ``MockerFixture.async_mock`` method. Thanks `@PerchunPak`_ for the PR (`#296`_).
.. _@PerchunPak: https://github.com/PerchunPak
.. _#296: https://github.com/pytest-dev/pytest-mock/pull/296
3.7.0 (2022-01-28) 3.7.0 (2022-01-28)
------------------ ------------------
@ -414,8 +439,7 @@ Breaking Changes
option. option.
Thanks `@asfaltboy`_ for the PR (`#36`_). Thanks `@asfaltboy`_ for the PR (`#36`_).
* ``mocker.stub()`` now allows passing in the name for the constructed `Mock * ``mocker.stub()`` now allows passing in the name for the constructed ``Mock``
<https://docs.python.org/3/library/unittest.mock.html#the-mock-class>`_
object instead of having to set it using the internal ``_mock_name`` attribute object instead of having to set it using the internal ``_mock_name`` attribute
directly. This is useful for debugging as the name is used in the mock's directly. This is useful for debugging as the name is used in the mock's
``repr`` string as well as related assertion failure messages. ``repr`` string as well as related assertion failure messages.

357
PKG-INFO
View file

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: pytest-mock Name: pytest-mock
Version: 3.7.0 Version: 3.8.2
Summary: Thin-wrapper around the mock package for easier use with pytest Summary: Thin-wrapper around the mock package for easier use with pytest
Home-page: https://github.com/pytest-dev/pytest-mock/ Home-page: https://github.com/pytest-dev/pytest-mock/
Author: Bruno Oliveira Author: Bruno Oliveira
@ -51,7 +51,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro
nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when
comparing calls. comparing calls.
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| |python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit|
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
:target: https://pypi.python.org/pypi/pytest-mock :target: https://pypi.python.org/pypi/pytest-mock
@ -59,7 +59,7 @@ comparing calls.
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
:target: https://anaconda.org/conda-forge/pytest-mock :target: https://anaconda.org/conda-forge/pytest-mock
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg .. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg
:target: https://github.com/pytest-dev/pytest-mock/actions :target: https://github.com/pytest-dev/pytest-mock/actions
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
@ -74,361 +74,22 @@ comparing calls.
.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master
`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>`_ .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest
:target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest
Usage
=====
The ``mocker`` fixture has the same API as `Professionally supported pytest-mock is available <https://tidelift.com/subscription/pkg/pypi-pytest_mock?utm_source=pypi-pytest-mock&utm_medium=referral&utm_campaign=readme>`_.
`mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_,
supporting the same arguments:
.. code-block:: python
def test_foo(mocker): Documentation
# 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: For full documentation, please see https://pytest-mock.readthedocs.io/en/latest.
* `mocker.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_
* `mocker.patch.object <https://docs.python.org/3/library/unittest.mock.html#patch-object>`_
* `mocker.patch.multiple <https://docs.python.org/3/library/unittest.mock.html#patch-multiple>`_
* `mocker.patch.dict <https://docs.python.org/3/library/unittest.mock.html#patch-dict>`_
* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_
* ``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.
Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``:
* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_
* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_
* `PropertyMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.PropertyMock>`_
* `ANY <https://docs.python.org/3/library/unittest.mock.html#any>`_
* `DEFAULT <https://docs.python.org/3/library/unittest.mock.html#default>`_ *(Version 1.4)*
* `call <https://docs.python.org/3/library/unittest.mock.html#call>`_ *(Version 1.1)*
* `sentinel <https://docs.python.org/3/library/unittest.mock.html#sentinel>`_ *(Version 1.2)*
* `mock_open <https://docs.python.org/3/library/unittest.mock.html#mock-open>`_
* `seal <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.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``
Type Annotations
----------------
*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:
.. code-block:: python
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.
Spy
---
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.
.. code-block:: python
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, or ``None`` 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).
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
Stub
----
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.
.. code-block:: python
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 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:
.. code-block:: ini
[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
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
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.
.. code-block:: ini
[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.
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:
.. code-block:: python
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
=======
Install using `pip <http://pip-installer.org/>`_:
.. code-block:: console
$ pip install pytest-mock
Changelog
=========
Please consult the `changelog page`_.
.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst
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;
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:
.. code-block:: python
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``.
Contributing
============
Contributions are welcome! After cloning the repository, create a virtual env
and install ``pytest-mock`` in editable mode with ``dev`` extras:
.. code-block:: console
$ pip install --editable .[dev]
$ pre-commit install
Tests are run with ``tox``, you can run the baseline environments before submitting a PR:
.. code-block:: console
$ tox -e py38,linting
Style checks and formatting are done automatically during commit courtesy of
`pre-commit <https://pre-commit.com>`_.
License License
======= =======
Distributed under the terms of the `MIT`_ license. Distributed under the terms of the `MIT`_ license.
Security contact information
============================
To report a security vulnerability, please use the `Tidelift security contact <https://tidelift.com/security>`__. Tidelift will coordinate the fix and disclosure.
.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE

View file

@ -25,7 +25,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro
nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when
comparing calls. comparing calls.
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| |python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit|
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
:target: https://pypi.python.org/pypi/pytest-mock :target: https://pypi.python.org/pypi/pytest-mock
@ -33,7 +33,7 @@ comparing calls.
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
:target: https://anaconda.org/conda-forge/pytest-mock :target: https://anaconda.org/conda-forge/pytest-mock
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg .. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg
:target: https://github.com/pytest-dev/pytest-mock/actions :target: https://github.com/pytest-dev/pytest-mock/actions
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
@ -48,359 +48,22 @@ comparing calls.
.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master
`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>`_ .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest
:target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest
Usage
=====
The ``mocker`` fixture has the same API as `Professionally supported pytest-mock is available <https://tidelift.com/subscription/pkg/pypi-pytest_mock?utm_source=pypi-pytest-mock&utm_medium=referral&utm_campaign=readme>`_.
`mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_,
supporting the same arguments:
.. code-block:: python
def test_foo(mocker): Documentation
# 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: For full documentation, please see https://pytest-mock.readthedocs.io/en/latest.
* `mocker.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_
* `mocker.patch.object <https://docs.python.org/3/library/unittest.mock.html#patch-object>`_
* `mocker.patch.multiple <https://docs.python.org/3/library/unittest.mock.html#patch-multiple>`_
* `mocker.patch.dict <https://docs.python.org/3/library/unittest.mock.html#patch-dict>`_
* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_
* ``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.
Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``:
* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_
* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_
* `PropertyMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.PropertyMock>`_
* `ANY <https://docs.python.org/3/library/unittest.mock.html#any>`_
* `DEFAULT <https://docs.python.org/3/library/unittest.mock.html#default>`_ *(Version 1.4)*
* `call <https://docs.python.org/3/library/unittest.mock.html#call>`_ *(Version 1.1)*
* `sentinel <https://docs.python.org/3/library/unittest.mock.html#sentinel>`_ *(Version 1.2)*
* `mock_open <https://docs.python.org/3/library/unittest.mock.html#mock-open>`_
* `seal <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.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``
Type Annotations
----------------
*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:
.. code-block:: python
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.
Spy
---
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.
.. code-block:: python
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, or ``None`` 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).
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
Stub
----
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.
.. code-block:: python
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 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:
.. code-block:: ini
[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
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
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.
.. code-block:: ini
[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.
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:
.. code-block:: python
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
=======
Install using `pip <http://pip-installer.org/>`_:
.. code-block:: console
$ pip install pytest-mock
Changelog
=========
Please consult the `changelog page`_.
.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst
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;
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:
.. code-block:: python
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``.
Contributing
============
Contributions are welcome! After cloning the repository, create a virtual env
and install ``pytest-mock`` in editable mode with ``dev`` extras:
.. code-block:: console
$ pip install --editable .[dev]
$ pre-commit install
Tests are run with ``tox``, you can run the baseline environments before submitting a PR:
.. code-block:: console
$ tox -e py38,linting
Style checks and formatting are done automatically during commit courtesy of
`pre-commit <https://pre-commit.com>`_.
License License
======= =======
Distributed under the terms of the `MIT`_ license. Distributed under the terms of the `MIT`_ license.
Security contact information
============================
To report a security vulnerability, please use the `Tidelift security contact <https://tidelift.com/security>`__. Tidelift will coordinate the fix and disclosure.
.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE

20
docs/about.rst Normal file
View file

@ -0,0 +1,20 @@
=====
About
=====
Tidelift
========
If you use this plugin in a corporate environment, consider supporting ``pytest-mock`` via `Tidelift <https://tidelift.com/subscription/pkg/pypi-pytest_mock?utm_source=pypi-pytest-mock&utm_medium=referral&utm_campaign=readme>`_.
License
=======
Distributed under the terms of the `MIT`_ license.
Security contact information
============================
To report a security vulnerability, please use the `Tidelift security contact <https://tidelift.com/security>`__. Tidelift will coordinate the fix and disclosure.
.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE

7
docs/changelog.rst Normal file
View file

@ -0,0 +1,7 @@
=========
Changelog
=========
:hide-toc:
.. include:: ../CHANGELOG.rst

50
docs/conf.py Normal file
View file

@ -0,0 +1,50 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = "pytest-mock"
copyright = "2022, Bruno Oliveira"
author = "Bruno Oliveira"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx_copybutton",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "furo"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]

75
docs/configuration.rst Normal file
View file

@ -0,0 +1,75 @@
=============
Configuration
=============
Use standalone "mock" package
-----------------------------
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.
.. code-block:: ini
[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+.
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 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:
.. code-block:: ini
[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
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html

21
docs/contributing.rst Normal file
View file

@ -0,0 +1,21 @@
============
Contributing
============
Contributions are welcome! After cloning the repository, create a virtual env
and install ``pytest-mock`` in editable mode with ``dev`` extras:
.. code-block:: console
$ pip install --editable .[dev]
$ pre-commit install
Tests are run with ``tox``, you can run the baseline environments before submitting a PR:
.. code-block:: console
$ tox -e py38,linting
Style checks and formatting are done automatically during commit courtesy of
`pre-commit <https://pre-commit.com>`_.

49
docs/index.rst Normal file
View file

@ -0,0 +1,49 @@
pytest-mock
===========
This `pytest`_ plugin provides a ``mocker`` fixture which is a thin-wrapper around the patching API
provided by the `mock package <http://pypi.python.org/pypi/mock>`_:
.. code-block:: python
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.
Install
=======
Install using `pip <http://pip-installer.org/>`_:
.. code-block:: console
$ pip install pytest-mock
.. _`pytest`: https://pytest.org
.. toctree::
:maxdepth: 2
:caption: Contents:
usage
configuration
remarks
contributing
about
changelog

122
docs/remarks.rst Normal file
View file

@ -0,0 +1,122 @@
=======
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:
.. code-block:: python
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:
.. 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;
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:
.. code-block:: python
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``.
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:
.. code-block:: python
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.

3
docs/requirements.txt Normal file
View file

@ -0,0 +1,3 @@
furo
sphinx
sphinx-copybutton

116
docs/usage.rst Normal file
View file

@ -0,0 +1,116 @@
=====
Usage
=====
The ``mocker`` fixture has the same API as
`mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_,
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 <https://docs.python.org/3/library/unittest.mock.html#patch>`_
* `mocker.patch.object <https://docs.python.org/3/library/unittest.mock.html#patch-object>`_
* `mocker.patch.multiple <https://docs.python.org/3/library/unittest.mock.html#patch-multiple>`_
* `mocker.patch.dict <https://docs.python.org/3/library/unittest.mock.html#patch-dict>`_
* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_
* ``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.
Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``:
* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_
* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_
* `PropertyMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.PropertyMock>`_
* `ANY <https://docs.python.org/3/library/unittest.mock.html#any>`_
* `DEFAULT <https://docs.python.org/3/library/unittest.mock.html#default>`_
* `call <https://docs.python.org/3/library/unittest.mock.html#call>`_
* `sentinel <https://docs.python.org/3/library/unittest.mock.html#sentinel>`_
* `mock_open <https://docs.python.org/3/library/unittest.mock.html#mock-open>`_
* `seal <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.seal>`_
It is also possible to use mocking functionality from fixtures of other scopes using
the appropriate fixture:
* ``class_mocker``
* ``module_mocker``
* ``package_mocker``
* ``session_mocker``
Spy
---
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.
.. code-block:: python
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, or ``None`` 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).
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
Stub
----
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.
.. code-block:: python
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')
.. seealso::
``async_stub`` method, which actually the same as ``stub`` but makes async stub.

View file

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: pytest-mock Name: pytest-mock
Version: 3.7.0 Version: 3.8.2
Summary: Thin-wrapper around the mock package for easier use with pytest Summary: Thin-wrapper around the mock package for easier use with pytest
Home-page: https://github.com/pytest-dev/pytest-mock/ Home-page: https://github.com/pytest-dev/pytest-mock/
Author: Bruno Oliveira Author: Bruno Oliveira
@ -51,7 +51,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro
nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when
comparing calls. comparing calls.
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| |python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit|
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
:target: https://pypi.python.org/pypi/pytest-mock :target: https://pypi.python.org/pypi/pytest-mock
@ -59,7 +59,7 @@ comparing calls.
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
:target: https://anaconda.org/conda-forge/pytest-mock :target: https://anaconda.org/conda-forge/pytest-mock
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg .. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg
:target: https://github.com/pytest-dev/pytest-mock/actions :target: https://github.com/pytest-dev/pytest-mock/actions
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
@ -74,361 +74,22 @@ comparing calls.
.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master
`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>`_ .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest
:target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest
Usage
=====
The ``mocker`` fixture has the same API as `Professionally supported pytest-mock is available <https://tidelift.com/subscription/pkg/pypi-pytest_mock?utm_source=pypi-pytest-mock&utm_medium=referral&utm_campaign=readme>`_.
`mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_,
supporting the same arguments:
.. code-block:: python
def test_foo(mocker): Documentation
# 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: For full documentation, please see https://pytest-mock.readthedocs.io/en/latest.
* `mocker.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`_
* `mocker.patch.object <https://docs.python.org/3/library/unittest.mock.html#patch-object>`_
* `mocker.patch.multiple <https://docs.python.org/3/library/unittest.mock.html#patch-multiple>`_
* `mocker.patch.dict <https://docs.python.org/3/library/unittest.mock.html#patch-dict>`_
* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_
* ``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.
Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``:
* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_
* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_
* `PropertyMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.PropertyMock>`_
* `ANY <https://docs.python.org/3/library/unittest.mock.html#any>`_
* `DEFAULT <https://docs.python.org/3/library/unittest.mock.html#default>`_ *(Version 1.4)*
* `call <https://docs.python.org/3/library/unittest.mock.html#call>`_ *(Version 1.1)*
* `sentinel <https://docs.python.org/3/library/unittest.mock.html#sentinel>`_ *(Version 1.2)*
* `mock_open <https://docs.python.org/3/library/unittest.mock.html#mock-open>`_
* `seal <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.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``
Type Annotations
----------------
*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:
.. code-block:: python
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.
Spy
---
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.
.. code-block:: python
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, or ``None`` 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).
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
Stub
----
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.
.. code-block:: python
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 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:
.. code-block:: ini
[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
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
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.
.. code-block:: ini
[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.
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:
.. code-block:: python
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
=======
Install using `pip <http://pip-installer.org/>`_:
.. code-block:: console
$ pip install pytest-mock
Changelog
=========
Please consult the `changelog page`_.
.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst
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;
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:
.. code-block:: python
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``.
Contributing
============
Contributions are welcome! After cloning the repository, create a virtual env
and install ``pytest-mock`` in editable mode with ``dev`` extras:
.. code-block:: console
$ pip install --editable .[dev]
$ pre-commit install
Tests are run with ``tox``, you can run the baseline environments before submitting a PR:
.. code-block:: console
$ tox -e py38,linting
Style checks and formatting are done automatically during commit courtesy of
`pre-commit <https://pre-commit.com>`_.
License License
======= =======
Distributed under the terms of the `MIT`_ license. Distributed under the terms of the `MIT`_ license.
Security contact information
============================
To report a security vulnerability, please use the `Tidelift security contact <https://tidelift.com/security>`__. Tidelift will coordinate the fix and disclosure.
.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE

View file

@ -8,7 +8,17 @@ mypy.ini
setup.py setup.py
tox.ini tox.ini
.github/FUNDING.yml .github/FUNDING.yml
.github/workflows/main.yml .github/workflows/deploy.yml
.github/workflows/test.yml
docs/about.rst
docs/changelog.rst
docs/conf.py
docs/configuration.rst
docs/contributing.rst
docs/index.rst
docs/remarks.rst
docs/requirements.txt
docs/usage.rst
scripts/gen-release-notes.py scripts/gen-release-notes.py
src/pytest_mock/__init__.py src/pytest_mock/__init__.py
src/pytest_mock/_util.py src/pytest_mock/_util.py

View file

@ -1,3 +1,2 @@
[pytest11] [pytest11]
pytest_mock = pytest_mock pytest_mock = pytest_mock

View file

@ -1,5 +1,5 @@
# coding: utf-8 # coding: utf-8
# file generated by setuptools_scm # file generated by setuptools_scm
# don't change, don't track in version control # don't change, don't track in version control
version = '3.7.0' __version__ = version = '3.8.2'
version_tuple = (3, 7, 0) __version_tuple__ = version_tuple = (3, 8, 2)

View file

@ -27,6 +27,11 @@ from ._util import parse_ini_boolean
_T = TypeVar("_T") _T = TypeVar("_T")
if sys.version_info[:2] > (3, 7):
AsyncMockType = unittest.mock.AsyncMock
else:
AsyncMockType = Any
class PytestMockWarning(UserWarning): class PytestMockWarning(UserWarning):
"""Base class for all warnings emitted by pytest-mock.""" """Base class for all warnings emitted by pytest-mock."""
@ -159,6 +164,19 @@ class MockerFixture:
self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name), self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name),
) )
def async_stub(self, name: Optional[str] = None) -> AsyncMockType:
"""
Create a async stub method. It accepts any arguments. Ideal to register to
callbacks in tests.
:param name: the constructed stub's name as used in repr
:return: Stub object.
"""
return cast(
AsyncMockType,
self.mock_module.AsyncMock(spec=lambda *args, **kwargs: None, name=name),
)
class _Patcher: class _Patcher:
""" """
Object to provide the same interface as mock.patch, mock.patch.object, Object to provide the same interface as mock.patch, mock.patch.object,

View file

@ -2,6 +2,7 @@ import os
import platform import platform
import re import re
import sys import sys
import warnings
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any from typing import Any
from typing import Callable from typing import Callable
@ -25,13 +26,16 @@ skip_pypy = pytest.mark.skipif(
# Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0 # Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0
NEW_FORMATTING = sys.version_info >= (3, 8) NEW_FORMATTING = sys.version_info >= (3, 8)
if sys.version_info[:2] >= (3, 8):
from unittest.mock import AsyncMock
@pytest.fixture @pytest.fixture
def needs_assert_rewrite(pytestconfig): def needs_assert_rewrite(pytestconfig):
""" """
Fixture which skips requesting test if assertion rewrite is disabled (#102) Fixture which skips requesting test if assertion rewrite is disabled (#102)
Making this a fixture to avoid acessing pytest's config in the global context. Making this a fixture to avoid accessing pytest's config in the global context.
""" """
option = pytestconfig.getoption("assertmode") option = pytestconfig.getoption("assertmode")
if option != "rewrite": if option != "rewrite":
@ -232,6 +236,13 @@ class TestMockerStub:
def test_failure_message_with_name(self, mocker: MagicMock, name: str) -> None: def test_failure_message_with_name(self, mocker: MagicMock, name: str) -> None:
self.__test_failure_message(mocker, name=name) self.__test_failure_message(mocker, name=name)
@pytest.mark.skipif(
sys.version_info[:2] < (3, 8),
reason="This Python version doesn't have `AsyncMock`.",
)
def test_async_stub_type(self, mocker: MockerFixture) -> None:
assert isinstance(mocker.async_stub(), AsyncMock)
def test_instance_method_spy(mocker: MockerFixture) -> None: def test_instance_method_spy(mocker: MockerFixture) -> None:
class Foo: class Foo:
@ -707,10 +718,14 @@ def test_monkeypatch_no_terminal(testdir: Any) -> None:
def test_standalone_mock(testdir: Any) -> None: def test_standalone_mock(testdir: Any) -> None:
"""Check that the "mock_use_standalone" is being used.""" """Check that the "mock_use_standalone" is being used."""
pytest.importorskip("mock")
testdir.makepyfile( testdir.makepyfile(
""" """
import mock
def test_foo(mocker): def test_foo(mocker):
pass assert mock.MagicMock is mocker.MagicMock
""" """
) )
testdir.makeini( testdir.makeini(
@ -720,8 +735,7 @@ def test_standalone_mock(testdir: Any) -> None:
""" """
) )
result = testdir.runpytest_subprocess() result = testdir.runpytest_subprocess()
assert result.ret == 3 assert result.ret == 0
result.stderr.fnmatch_lines(["*No module named 'mock'*"])
@pytest.mark.usefixtures("needs_assert_rewrite") @pytest.mark.usefixtures("needs_assert_rewrite")
@ -758,12 +772,12 @@ def test_detailed_introspection(testdir: Any) -> None:
"*Args:", "*Args:",
"*assert ('fo',) == ('',)", "*assert ('fo',) == ('',)",
"*At index 0 diff: 'fo' != ''*", "*At index 0 diff: 'fo' != ''*",
"*Use -v to get the full diff*", "*Use -v to get more diff*",
"*Kwargs:*", "*Kwargs:*",
"*assert {} == {'bar': 4}*", "*assert {} == {'bar': 4}*",
"*Right contains* more item*", "*Right contains* more item*",
"*{'bar': 4}*", "*{'bar': 4}*",
"*Use -v to get the full diff*", "*Use -v to get more diff*",
] ]
result.stdout.fnmatch_lines(expected_lines) result.stdout.fnmatch_lines(expected_lines)
@ -799,12 +813,12 @@ def test_detailed_introspection_async(testdir: Any) -> None:
"*Args:", "*Args:",
"*assert ('fo',) == ('',)", "*assert ('fo',) == ('',)",
"*At index 0 diff: 'fo' != ''*", "*At index 0 diff: 'fo' != ''*",
"*Use -v to get the full diff*", "*Use -v to get more diff*",
"*Kwargs:*", "*Kwargs:*",
"*assert {} == {'bar': 4}*", "*assert {} == {'bar': 4}*",
"*Right contains* more item*", "*Right contains* more item*",
"*{'bar': 4}*", "*{'bar': 4}*",
"*Use -v to get the full diff*", "*Use -v to get more diff*",
] ]
result.stdout.fnmatch_lines(expected_lines) result.stdout.fnmatch_lines(expected_lines)
@ -929,7 +943,7 @@ def test_patch_context_manager_with_context_manager(mocker: MockerFixture) -> No
a = A() a = A()
with pytest.warns(None) as warn_record: with warnings.catch_warnings(record=True) as warn_record:
with mocker.patch.context_manager(a, "doIt", return_value=True): with mocker.patch.context_manager(a, "doIt", return_value=True):
assert a.doIt() is True assert a.doIt() is True

View file

@ -6,6 +6,7 @@ envlist = py{37,38,39,310}, norewrite
passenv = USER USERNAME passenv = USER USERNAME
deps = deps =
coverage coverage
mock
pytest-asyncio pytest-asyncio
commands = commands =
coverage run --append --source={envsitepackagesdir}/pytest_mock -m pytest tests coverage run --append --source={envsitepackagesdir}/pytest_mock -m pytest tests
@ -20,3 +21,10 @@ asyncio_mode = auto
[flake8] [flake8]
max-line-length = 88 max-line-length = 88
[testenv:docs]
usedevelop = True
deps =
-r docs/requirements.txt
commands =
sphinx-build -W --keep-going -b html docs docs/_build/html {posargs:}