New upstream version 3.8.2
This commit is contained in:
parent
6ed0752e75
commit
b4297c2616
39
.github/workflows/deploy.yml
vendored
Normal file
39
.github/workflows/deploy.yml
vendored
Normal 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
|
72
.github/workflows/main.yml
vendored
72
.github/workflows/main.yml
vendored
|
@ -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
38
.github/workflows/test.yml
vendored
Normal 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 }}
|
|
@ -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)
|
||||||
|
|
|
@ -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
357
PKG-INFO
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
353
README.rst
353
README.rst
|
@ -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
20
docs/about.rst
Normal 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
7
docs/changelog.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
=========
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
:hide-toc:
|
||||||
|
|
||||||
|
.. include:: ../CHANGELOG.rst
|
50
docs/conf.py
Normal file
50
docs/conf.py
Normal 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
75
docs/configuration.rst
Normal 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
21
docs/contributing.rst
Normal 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
49
docs/index.rst
Normal 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
122
docs/remarks.rst
Normal 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
3
docs/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
furo
|
||||||
|
sphinx
|
||||||
|
sphinx-copybutton
|
116
docs/usage.rst
Normal file
116
docs/usage.rst
Normal 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.
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
[pytest11]
|
[pytest11]
|
||||||
pytest_mock = pytest_mock
|
pytest_mock = pytest_mock
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -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:}
|
||||||
|
|
Loading…
Reference in a new issue