New upstream version 3.6.1
This commit is contained in:
parent
e5f34a66ea
commit
d47623bc9f
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# info:
|
||||
# * https://help.github.com/en/articles/displaying-a-sponsor-button-in-your-repository
|
||||
# * https://tidelift.com/subscription/how-to-connect-tidelift-with-github
|
||||
tidelift: pypi/pytest-mock
|
64
.github/workflows/main.yml
vendored
Normal file
64
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: ["3.6", "3.7", "3.8", "3.9"]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- python: "3.6"
|
||||
tox_env: "py36"
|
||||
- python: "3.7"
|
||||
tox_env: "py37"
|
||||
- python: "3.8"
|
||||
tox_env: "py38"
|
||||
- python: "3.9"
|
||||
tox_env: "py39"
|
||||
|
||||
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 wheel
|
||||
- name: Build package
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_token }}
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -56,7 +56,7 @@ docs/_build/
|
|||
# Virtual Envs
|
||||
.env*
|
||||
|
||||
_pytest_mock_version.py
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
/src/pytest_mock/_version.py
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
exclude: '^($|.*\.bin)'
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.6b4
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.4b2
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3.6
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v1.3.0
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
@ -19,4 +18,15 @@ repos:
|
|||
files: ^(CHANGELOG.rst|README.rst|HOWTORELEASE.rst|changelog/.*)$
|
||||
language: python
|
||||
additional_dependencies: [pygments, restructuredtext_lint]
|
||||
python_version: python3.6
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.812 # NOTE: keep this in sync with tox.ini
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(src|tests)
|
||||
args: []
|
||||
additional_dependencies: [pytest>=6]
|
||||
|
|
50
.travis.yml
50
.travis.yml
|
@ -1,50 +0,0 @@
|
|||
language: python
|
||||
dist: xenial
|
||||
|
||||
install:
|
||||
- pip install -U pip
|
||||
- pip install tox coveralls
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
|
||||
stages:
|
||||
- test
|
||||
- name: deploy
|
||||
if: repo = pytest-dev/pytest-mock AND tag IS present
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- python: '2.7'
|
||||
env: TOXENV=py27
|
||||
- python: '3.4'
|
||||
env: TOXENV=py34
|
||||
- python: '3.5'
|
||||
env: TOXENV=py35
|
||||
- python: '3.6'
|
||||
env: TOXENV=py36
|
||||
- python: '3.7'
|
||||
env: TOXENV=py37
|
||||
- python: '3.8-dev'
|
||||
env: TOXENV=py38
|
||||
- python: '3.6'
|
||||
env: TOXENV=linting
|
||||
- python: '3.6'
|
||||
env: TOXENV=norewrite
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
install: pip install -U setuptools setuptools_scm
|
||||
script: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: nicoddemus
|
||||
skip_upload_docs: true
|
||||
distributions: sdist bdist_wheel
|
||||
password:
|
||||
secure: OEWrbk09CZRrwFE6sBpRqQHu45zRu1S0Ly1ZeprkFCKxMd9tZOnrYM5qxCDQXxFHIvuyajuJ+qWTOgxUvurQMNsD6DbvJKTJ0R8upH1b1Q95KK8xiJFedhqBEUga5GrInK59oo0Sgblse2jtH5NnHXRUClSdT+iHdLY5sljCTRg=
|
||||
on:
|
||||
tags: true
|
||||
repo: pytest-dev/pytest-mock
|
223
CHANGELOG.rst
223
CHANGELOG.rst
|
@ -1,3 +1,226 @@
|
|||
3.6.1 (2021-05-06)
|
||||
------------------
|
||||
|
||||
* Fix ``mocker.resetall()`` when using ``mocker.spy()`` (`#237`_). Thanks `@blaxter`_ for the report and `@shadycuz`_ for the PR.
|
||||
|
||||
.. _@blaxter: https://github.com/blaxter
|
||||
.. _@shadycuz: https://github.com/shadycuz
|
||||
.. _#237: https://github.com/pytest-dev/pytest-mock/issues/237
|
||||
|
||||
3.6.0 (2021-04-24)
|
||||
------------------
|
||||
|
||||
* pytest-mock no longer supports Python 3.5.
|
||||
|
||||
* Correct type annotations for ``mocker.patch.object`` to also include the string form.
|
||||
Thanks `@plannigan`_ for the PR (`#235`_).
|
||||
|
||||
* ``reset_all`` now supports ``return_value`` and ``side_effect`` keyword arguments. Thanks `@alex-marty`_ for the PR (`#214`_).
|
||||
|
||||
.. _@alex-marty: https://github.com/alex-marty
|
||||
.. _@plannigan: https://github.com/plannigan
|
||||
.. _#214: https://github.com/pytest-dev/pytest-mock/pull/214
|
||||
.. _#235: https://github.com/pytest-dev/pytest-mock/pull/235
|
||||
|
||||
3.5.1 (2021-01-10)
|
||||
------------------
|
||||
|
||||
* Use ``inspect.getattr_static`` instead of resorting to ``object.__getattribute__``
|
||||
magic. This should better comply with objects which implement a custom descriptor
|
||||
protocol. Thanks `@yesthesoup`_ for the PR (`#224`_).
|
||||
|
||||
.. _@yesthesoup: https://github.com/yesthesoup
|
||||
.. _#224: https://github.com/pytest-dev/pytest-mock/pull/224
|
||||
|
||||
3.5.0 (2021-01-04)
|
||||
------------------
|
||||
|
||||
* Now all patch functions will emit a warning instead of raising a ``ValueError`` when used
|
||||
as a context-manager. Thanks `@iforapsy`_ for the PR (`#221`_).
|
||||
|
||||
* Additionally, ``mocker.patch.context_manager`` is available when the user intends to mock
|
||||
a context manager (for example ``threading.Lock`` object), which will not emit that
|
||||
warning.
|
||||
|
||||
.. _@iforapsy: https://github.com/iforapsy
|
||||
.. _#221: https://github.com/pytest-dev/pytest-mock/pull/221
|
||||
|
||||
3.4.0 (2020-12-15)
|
||||
------------------
|
||||
|
||||
* Add `mock.seal` alias to the `mocker` fixture (`#211`_). Thanks `@coiax`_ for the PR.
|
||||
|
||||
* Fixed spying on exceptions not covered by the ``Exception``
|
||||
superclass (`#215`_), like ``KeyboardInterrupt`` -- PR `#216`_
|
||||
by `@webknjaz`_.
|
||||
|
||||
Before the fix, both ``spy_return`` and ``spy_exception``
|
||||
were always assigned to ``None``
|
||||
whenever such an exception happened. And after this fix,
|
||||
``spy_exception`` is set to a correct value of an exception
|
||||
that has actually happened.
|
||||
|
||||
.. _@coiax: https://github.com/coiax
|
||||
.. _@webknjaz: https://github.com/sponsors/webknjaz
|
||||
.. _#211: https://github.com/pytest-dev/pytest-mock/pull/211
|
||||
.. _#215: https://github.com/pytest-dev/pytest-mock/issues/215
|
||||
.. _#216: https://github.com/pytest-dev/pytest-mock/pull/216
|
||||
|
||||
3.3.1 (2020-08-24)
|
||||
------------------
|
||||
|
||||
* Introduce ``MockFixture`` as an alias to ``MockerFixture``.
|
||||
|
||||
Before ``3.3.0``, the fixture class was named ``MockFixture``, but was renamed to ``MockerFixture`` to better
|
||||
match the ``mocker`` fixture. While not officially part of the API, it was later discovered that this broke
|
||||
the code of some users which already imported ``pytest_mock.MockFixture`` for type annotations, so we
|
||||
decided to reintroduce the name as an alias.
|
||||
|
||||
Note however that this is just a stop gap measure, and new code should use ``MockerFixture`` for type annotations.
|
||||
|
||||
* Improved typing for ``MockerFixture.patch`` (`#201`_). Thanks `@srittau`_ for the PR.
|
||||
|
||||
.. _@srittau: https://github.com/srittau
|
||||
.. _#201: https://github.com/pytest-dev/pytest-mock/pull/201
|
||||
|
||||
|
||||
3.3.0 (2020-08-21)
|
||||
------------------
|
||||
|
||||
* ``pytest-mock`` now includes inline type annotations and exposes them to user programs. The ``mocker`` fixture returns ``pytest_mock.MockerFixture``, which can be used to annotate your tests:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
def test_foo(mocker: MockerFixture) -> None:
|
||||
...
|
||||
|
||||
The type annotations were developed against mypy version ``0.782``, the
|
||||
minimum version supported at the moment. If you run into an error that you believe to be incorrect, please open an issue.
|
||||
|
||||
Many thanks to `@staticdev`_ for providing the initial patch (`#199`_).
|
||||
|
||||
.. _@staticdev: https://github.com/staticdev
|
||||
.. _#199: https://github.com/pytest-dev/pytest-mock/pull/199
|
||||
|
||||
3.2.0 (2020-07-11)
|
||||
------------------
|
||||
|
||||
* `AsyncMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.AsyncMock>`__ is now exposed in ``mocker`` and supports provides assertion introspection similar to ``Mock`` objects.
|
||||
|
||||
Added by `@tirkarthi`_ in `#197`_.
|
||||
|
||||
.. _@tirkarthi: https://github.com/tirkarthi
|
||||
.. _#197: https://github.com/pytest-dev/pytest-mock/pull/197
|
||||
|
||||
3.1.1 (2020-05-31)
|
||||
------------------
|
||||
|
||||
* Fixed performance regression caused by the ``ValueError`` raised
|
||||
when ``mocker`` is used as context manager (`#191`_).
|
||||
|
||||
.. _#191: https://github.com/pytest-dev/pytest-mock/issues/191
|
||||
|
||||
3.1.0 (2020-04-18)
|
||||
------------------
|
||||
|
||||
* New mocker fixtures added that allow using mocking functionality in other scopes:
|
||||
|
||||
* ``class_mocker``
|
||||
* ``module_mocker``
|
||||
* ``package_mocker``
|
||||
* ``session_mocker``
|
||||
|
||||
Added by `@scorphus`_ in `#182`_.
|
||||
|
||||
.. _@scorphus: https://github.com/scorphus
|
||||
.. _#182: https://github.com/pytest-dev/pytest-mock/pull/182
|
||||
|
||||
3.0.0 (2020-03-31)
|
||||
------------------
|
||||
|
||||
* Python 2.7 and 3.4 are no longer supported. Users using ``pip 9`` or later will install
|
||||
a compatible version automatically.
|
||||
|
||||
* ``mocker.spy`` now also works with ``async def`` functions (`#179`_). Thanks `@frankie567`_ for the PR!
|
||||
|
||||
.. _#179: https://github.com/pytest-dev/pytest-mock/issues/179
|
||||
.. _@frankie567: https://github.com/frankie567
|
||||
|
||||
2.0.0 (2020-01-04)
|
||||
------------------
|
||||
|
||||
Breaking Changes
|
||||
++++++++++++++++
|
||||
|
||||
* ``mocker.spy`` attributes for tracking returned values and raised exceptions of its spied functions
|
||||
are now called ``spy_return`` and ``spy_exception``, instead of reusing the existing
|
||||
``MagicMock`` attributes ``return_value`` and ``side_effect``.
|
||||
|
||||
Version ``1.13`` introduced a serious regression: after a spied function using ``mocker.spy``
|
||||
raises an exception, further calls to the spy will not call the spied function,
|
||||
always raising the first exception instead: assigning to ``side_effect`` causes
|
||||
``unittest.mock`` to behave this way (`#175`_).
|
||||
|
||||
* The deprecated ``mock`` alias to the ``mocker`` fixture has finally been removed.
|
||||
|
||||
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
|
||||
|
||||
|
||||
1.13.0 (2019-12-05)
|
||||
-------------------
|
||||
|
||||
* The object returned by ``mocker.spy`` now also tracks any side effect
|
||||
of the spied method/function.
|
||||
|
||||
1.12.1 (2019-11-20)
|
||||
-------------------
|
||||
|
||||
* Fix error if ``mocker.patch`` is used in code where the source file
|
||||
is not available, for example stale ``.pyc`` files (`#169`_).
|
||||
|
||||
.. _#169: https://github.com/pytest-dev/pytest-mock/issues/169#issuecomment-555729265
|
||||
|
||||
1.12.0 (2019-11-19)
|
||||
-------------------
|
||||
|
||||
* Now all patch functions also raise a ``ValueError`` when used
|
||||
as a context-manager. Thanks `@AlexGascon`_ for the PR (`#168`_).
|
||||
|
||||
.. _@AlexGascon: https://github.com/AlexGascon
|
||||
.. _#168: https://github.com/pytest-dev/pytest-mock/pull/168
|
||||
|
||||
1.11.2 (2019-10-19)
|
||||
-------------------
|
||||
|
||||
* The *pytest introspection follows* message is no longer shown
|
||||
if there is no pytest introspection (`#154`_).
|
||||
Thanks `@The-Compiler`_ for the report.
|
||||
|
||||
* ``mocker`` now raises a ``ValueError`` when used as a context-manager.
|
||||
Thanks `@binarymason`_ for the PR (`#165`_).
|
||||
|
||||
.. _#154: https://github.com/pytest-dev/pytest-mock/issues/154
|
||||
.. _#165: https://github.com/pytest-dev/pytest-mock/pull/165
|
||||
.. _@binarymason: https://github.com/binarymason
|
||||
|
||||
1.11.1 (2019-10-04)
|
||||
-------------------
|
||||
|
||||
* Fix ``mocker.spy`` on Python 2 when used on non-function objects
|
||||
which implement ``__call__`` (`#157`_). Thanks `@pbasista`_ for
|
||||
the report.
|
||||
|
||||
.. _#157: https://github.com/pytest-dev/pytest-mock/issues/157
|
||||
.. _@pbasista: https://github.com/pbasista
|
||||
|
||||
1.11.0
|
||||
------
|
||||
|
||||
* The object returned by ``mocker.spy`` now also tracks the return value
|
||||
of the spied method/function.
|
||||
|
||||
1.10.4
|
||||
------
|
||||
|
||||
|
|
209
PKG-INFO
209
PKG-INFO
|
@ -1,7 +1,7 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: pytest-mock
|
||||
Version: 1.10.4
|
||||
Summary: Thin-wrapper around the mock package for easier use with py.test
|
||||
Version: 3.6.1
|
||||
Summary: Thin-wrapper around the mock package for easier use with pytest
|
||||
Home-page: https://github.com/pytest-dev/pytest-mock/
|
||||
Author: Bruno Oliveira
|
||||
Author-email: nicoddemus@gmail.com
|
||||
|
@ -10,10 +10,8 @@ Description: ===========
|
|||
pytest-mock
|
||||
===========
|
||||
|
||||
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
|
||||
provided by the `mock package <http://pypi.python.org/pypi/mock>`_,
|
||||
but with the benefit of not having to worry about undoing patches at the end
|
||||
of a test:
|
||||
This 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
|
||||
|
||||
|
@ -31,8 +29,11 @@ Description: ===========
|
|||
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.
|
||||
|
||||
|python| |version| |anaconda| |ci| |appveyor| |coverage| |black|
|
||||
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit|
|
||||
|
||||
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock
|
||||
|
@ -40,14 +41,11 @@ Description: ===========
|
|||
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest-mock
|
||||
|
||||
.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.svg
|
||||
:target: https://travis-ci.org/pytest-dev/pytest-mock
|
||||
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest-mock/actions
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest-mock
|
||||
|
||||
.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest-mock
|
||||
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/pytest-dev/pytest-mock?branch=master
|
||||
|
||||
.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock/
|
||||
|
@ -55,6 +53,9 @@ Description: ===========
|
|||
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
|
||||
.. |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
|
||||
|
||||
`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>`_
|
||||
|
||||
Usage
|
||||
|
@ -81,7 +82,7 @@ Description: ===========
|
|||
* `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.
|
||||
|
||||
These objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
|
||||
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>`_
|
||||
|
@ -91,33 +92,93 @@ Description: ===========
|
|||
* `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 spy acts exactly like the original method in all cases, except it allows use of `mock`
|
||||
features with it, like retrieving call count. It also works for class and static methods.
|
||||
|
||||
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(mocker):
|
||||
def test_spy_method(mocker):
|
||||
class Foo(object):
|
||||
def bar(self):
|
||||
return 42
|
||||
def bar(self, v):
|
||||
return v * 2
|
||||
|
||||
foo = Foo()
|
||||
mocker.spy(foo, 'bar')
|
||||
assert foo.bar() == 42
|
||||
assert foo.bar.call_count == 1
|
||||
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, for instance.
|
||||
May be passed a name to be used by the constructed stub object in its repr (useful for debugging).
|
||||
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
|
||||
|
||||
|
@ -134,18 +195,17 @@ Description: ===========
|
|||
Improved reporting of mock call assertion errors
|
||||
------------------------------------------------
|
||||
|
||||
|
||||
This plugin monkeypatches the mock library to improve pytest output for failures
|
||||
of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback
|
||||
entries from the ``mock`` module.
|
||||
|
||||
It also adds introspection information on differing call arguments when
|
||||
calling the helper methods. This features catches `AssertionError` raised in
|
||||
the method, and uses py.test's own `advanced assertions`_ to return a better
|
||||
the method, and uses pytest's own `advanced assertions`_ to return a better
|
||||
diff::
|
||||
|
||||
|
||||
mocker = <pytest_mock.MockFixture object at 0x0381E2D0>
|
||||
mocker = <pytest_mock.MockerFixture object at 0x0381E2D0>
|
||||
|
||||
def test(mocker):
|
||||
m = mocker.Mock()
|
||||
|
@ -186,7 +246,7 @@ Description: ===========
|
|||
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://pytest.org/latest/assert.html
|
||||
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
|
||||
|
||||
|
||||
Use standalone "mock" package
|
||||
|
@ -206,13 +266,25 @@ Description: ===========
|
|||
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
|
||||
-----------------------------------
|
||||
|
||||
Requirements
|
||||
============
|
||||
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:
|
||||
|
||||
* Python 2.7, Python 3.4+
|
||||
* pytest
|
||||
* mock (for Python 2)
|
||||
.. 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
|
||||
|
@ -285,47 +357,29 @@ Description: ===========
|
|||
naming fixtures as parameters, or ``pytest.mark.parametrize``;
|
||||
- you can't easily undo the mocking during the test execution;
|
||||
|
||||
|
||||
**Note about usage as context manager**
|
||||
|
||||
Although mocker's API is intentionally the same as ``mock.patch``'s, its use
|
||||
as context manager and function decorator is **not** supported through the
|
||||
fixture. The purpose of this plugin is to make the use of context managers and
|
||||
function decorators for mocking unnecessary. Indeed, trying to use the
|
||||
functionality in ``mocker`` in this manner can lead to non-intuitive errors:
|
||||
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
|
||||
|
||||
def test_context_manager(mocker):
|
||||
a = A()
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
assert a.doIt() == True
|
||||
import contextlib
|
||||
import mock
|
||||
|
||||
.. code-block:: console
|
||||
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')
|
||||
|
||||
================================== FAILURES ===================================
|
||||
____________________________ test_context_manager _____________________________
|
||||
in test_context_manager
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
E AttributeError: __exit__
|
||||
stack.enter_context(mock.patch('os.listdir'))
|
||||
assert UnixFS.ls('dir') == expected
|
||||
# ...
|
||||
|
||||
You can however use ``mocker.mock_module`` to access the underlying ``mock``
|
||||
module, e.g. to return a context manager in a fixture that mocks something
|
||||
temporarily:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def fixture_cm(mocker):
|
||||
@contextlib.contextmanager
|
||||
def my_cm():
|
||||
def mocked():
|
||||
pass
|
||||
|
||||
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
|
||||
yield
|
||||
return my_cm
|
||||
stack.enter_context(mock.patch('shutil.copy'))
|
||||
UnixFS.cp('src', 'dst')
|
||||
# ...
|
||||
|
||||
But this is arguably a little more complex than using ``pytest-mock``.
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
@ -342,7 +396,7 @@ Description: ===========
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ tox -e py27,py36,linting
|
||||
$ tox -e py38,linting
|
||||
|
||||
Style checks and formatting are done automatically during commit courtesy of
|
||||
`pre-commit <https://pre-commit.com>`_.
|
||||
|
@ -352,6 +406,11 @@ Description: ===========
|
|||
|
||||
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
|
||||
|
||||
Keywords: pytest mock
|
||||
|
@ -361,14 +420,12 @@ Classifier: Framework :: Pytest
|
|||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=3.6
|
||||
Provides-Extra: dev
|
||||
|
|
197
README.rst
197
README.rst
|
@ -2,10 +2,8 @@
|
|||
pytest-mock
|
||||
===========
|
||||
|
||||
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
|
||||
provided by the `mock package <http://pypi.python.org/pypi/mock>`_,
|
||||
but with the benefit of not having to worry about undoing patches at the end
|
||||
of a test:
|
||||
This 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
|
||||
|
||||
|
@ -23,8 +21,11 @@ of a test:
|
|||
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.
|
||||
|
||||
|python| |version| |anaconda| |ci| |appveyor| |coverage| |black|
|
||||
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit|
|
||||
|
||||
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock
|
||||
|
@ -32,14 +33,11 @@ of a test:
|
|||
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest-mock
|
||||
|
||||
.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.svg
|
||||
:target: https://travis-ci.org/pytest-dev/pytest-mock
|
||||
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest-mock/actions
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest-mock
|
||||
|
||||
.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest-mock
|
||||
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/pytest-dev/pytest-mock?branch=master
|
||||
|
||||
.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock/
|
||||
|
@ -47,6 +45,9 @@ of a test:
|
|||
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
|
||||
.. |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
|
||||
|
||||
`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>`_
|
||||
|
||||
Usage
|
||||
|
@ -73,7 +74,7 @@ The supported methods are:
|
|||
* `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.
|
||||
|
||||
These objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
|
||||
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>`_
|
||||
|
@ -83,33 +84,93 @@ These objects from the ``mock`` module are accessible directly from ``mocker`` f
|
|||
* `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 spy acts exactly like the original method in all cases, except it allows use of `mock`
|
||||
features with it, like retrieving call count. It also works for class and static methods.
|
||||
|
||||
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(mocker):
|
||||
def test_spy_method(mocker):
|
||||
class Foo(object):
|
||||
def bar(self):
|
||||
return 42
|
||||
def bar(self, v):
|
||||
return v * 2
|
||||
|
||||
foo = Foo()
|
||||
mocker.spy(foo, 'bar')
|
||||
assert foo.bar() == 42
|
||||
assert foo.bar.call_count == 1
|
||||
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, for instance.
|
||||
May be passed a name to be used by the constructed stub object in its repr (useful for debugging).
|
||||
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
|
||||
|
||||
|
@ -126,18 +187,17 @@ May be passed a name to be used by the constructed stub object in its repr (usef
|
|||
Improved reporting of mock call assertion errors
|
||||
------------------------------------------------
|
||||
|
||||
|
||||
This plugin monkeypatches the mock library to improve pytest output for failures
|
||||
of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback
|
||||
entries from the ``mock`` module.
|
||||
|
||||
It also adds introspection information on differing call arguments when
|
||||
calling the helper methods. This features catches `AssertionError` raised in
|
||||
the method, and uses py.test's own `advanced assertions`_ to return a better
|
||||
the method, and uses pytest's own `advanced assertions`_ to return a better
|
||||
diff::
|
||||
|
||||
|
||||
mocker = <pytest_mock.MockFixture object at 0x0381E2D0>
|
||||
mocker = <pytest_mock.MockerFixture object at 0x0381E2D0>
|
||||
|
||||
def test(mocker):
|
||||
m = mocker.Mock()
|
||||
|
@ -178,7 +238,7 @@ Note that this feature is automatically disabled with the ``--tb=native`` option
|
|||
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://pytest.org/latest/assert.html
|
||||
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
|
||||
|
||||
|
||||
Use standalone "mock" package
|
||||
|
@ -198,13 +258,25 @@ This will force the plugin to import ``mock`` instead of the ``unittest.mock`` m
|
|||
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
|
||||
-----------------------------------
|
||||
|
||||
Requirements
|
||||
============
|
||||
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:
|
||||
|
||||
* Python 2.7, Python 3.4+
|
||||
* pytest
|
||||
* mock (for Python 2)
|
||||
.. 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
|
||||
|
@ -277,47 +349,29 @@ But this poses a few disadvantages:
|
|||
naming fixtures as parameters, or ``pytest.mark.parametrize``;
|
||||
- you can't easily undo the mocking during the test execution;
|
||||
|
||||
|
||||
**Note about usage as context manager**
|
||||
|
||||
Although mocker's API is intentionally the same as ``mock.patch``'s, its use
|
||||
as context manager and function decorator is **not** supported through the
|
||||
fixture. The purpose of this plugin is to make the use of context managers and
|
||||
function decorators for mocking unnecessary. Indeed, trying to use the
|
||||
functionality in ``mocker`` in this manner can lead to non-intuitive errors:
|
||||
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
|
||||
|
||||
def test_context_manager(mocker):
|
||||
a = A()
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
assert a.doIt() == True
|
||||
import contextlib
|
||||
import mock
|
||||
|
||||
.. code-block:: console
|
||||
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')
|
||||
|
||||
================================== FAILURES ===================================
|
||||
____________________________ test_context_manager _____________________________
|
||||
in test_context_manager
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
E AttributeError: __exit__
|
||||
stack.enter_context(mock.patch('os.listdir'))
|
||||
assert UnixFS.ls('dir') == expected
|
||||
# ...
|
||||
|
||||
You can however use ``mocker.mock_module`` to access the underlying ``mock``
|
||||
module, e.g. to return a context manager in a fixture that mocks something
|
||||
temporarily:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def fixture_cm(mocker):
|
||||
@contextlib.contextmanager
|
||||
def my_cm():
|
||||
def mocked():
|
||||
pass
|
||||
|
||||
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
|
||||
yield
|
||||
return my_cm
|
||||
stack.enter_context(mock.patch('shutil.copy'))
|
||||
UnixFS.cp('src', 'dst')
|
||||
# ...
|
||||
|
||||
But this is arguably a little more complex than using ``pytest-mock``.
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
@ -334,7 +388,7 @@ Tests are run with ``tox``, you can run the baseline environments before submitt
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ tox -e py27,py36,linting
|
||||
$ tox -e py38,linting
|
||||
|
||||
Style checks and formatting are done automatically during commit courtesy of
|
||||
`pre-commit <https://pre-commit.com>`_.
|
||||
|
@ -344,4 +398,9 @@ 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
|
||||
|
|
10
appveyor.yml
10
appveyor.yml
|
@ -1,10 +0,0 @@
|
|||
install:
|
||||
- C:\Python37\python -m pip install -U pip
|
||||
- C:\Python37\python -m pip install tox
|
||||
|
||||
build: false # Not a C# project
|
||||
|
||||
test_script:
|
||||
- C:\Python37\scripts\tox
|
||||
|
||||
skip_tags: true
|
13
mypy.ini
Normal file
13
mypy.ini
Normal file
|
@ -0,0 +1,13 @@
|
|||
[mypy]
|
||||
disallow_any_generics = True
|
||||
disallow_incomplete_defs = True
|
||||
disallow_subclassing_any = True
|
||||
no_implicit_optional = True
|
||||
pretty = True
|
||||
show_error_codes = True
|
||||
strict_equality = True
|
||||
warn_redundant_casts = True
|
||||
warn_return_any = True
|
||||
warn_unreachable = True
|
||||
warn_unused_configs = True
|
||||
warn_unused_ignores = True
|
|
@ -1,20 +0,0 @@
|
|||
.gitignore
|
||||
.pre-commit-config.yaml
|
||||
.travis.yml
|
||||
CHANGELOG.rst
|
||||
HOWTORELEASE.rst
|
||||
LICENSE
|
||||
README.rst
|
||||
_pytest_mock_version.py
|
||||
appveyor.yml
|
||||
pytest_mock.py
|
||||
setup.cfg
|
||||
setup.py
|
||||
test_pytest_mock.py
|
||||
tox.ini
|
||||
pytest_mock.egg-info/PKG-INFO
|
||||
pytest_mock.egg-info/SOURCES.txt
|
||||
pytest_mock.egg-info/dependency_links.txt
|
||||
pytest_mock.egg-info/entry_points.txt
|
||||
pytest_mock.egg-info/requires.txt
|
||||
pytest_mock.egg-info/top_level.txt
|
|
@ -1,8 +0,0 @@
|
|||
pytest>=2.7
|
||||
|
||||
[:python_version < "3.0"]
|
||||
mock
|
||||
|
||||
[dev]
|
||||
pre-commit
|
||||
tox
|
|
@ -1,2 +0,0 @@
|
|||
_pytest_mock_version
|
||||
pytest_mock
|
336
pytest_mock.py
336
pytest_mock.py
|
@ -1,336 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from _pytest_mock_version import version
|
||||
|
||||
__version__ = version
|
||||
|
||||
# pseudo-six; if this starts to require more than this, depend on six already
|
||||
if sys.version_info[0] == 2: # pragma: no cover
|
||||
text_type = unicode # noqa
|
||||
else:
|
||||
text_type = str
|
||||
|
||||
|
||||
def _get_mock_module(config):
|
||||
"""
|
||||
Import and return the actual "mock" module. By default this is "mock" for Python 2 and
|
||||
"unittest.mock" for Python 3, but the user can force to always use "mock" on Python 3 using
|
||||
the mock_use_standalone_module ini option.
|
||||
"""
|
||||
if not hasattr(_get_mock_module, "_module"):
|
||||
use_standalone_module = parse_ini_boolean(
|
||||
config.getini("mock_use_standalone_module")
|
||||
)
|
||||
if sys.version_info[0] == 2 or use_standalone_module:
|
||||
import mock
|
||||
|
||||
_get_mock_module._module = mock
|
||||
else:
|
||||
import unittest.mock
|
||||
|
||||
_get_mock_module._module = unittest.mock
|
||||
|
||||
return _get_mock_module._module
|
||||
|
||||
|
||||
class MockFixture(object):
|
||||
"""
|
||||
Fixture that provides the same interface to functions in the mock module,
|
||||
ensuring that they are uninstalled at the end of each test.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self._patches = [] # list of mock._patch objects
|
||||
self._mocks = [] # list of MagicMock objects
|
||||
self.mock_module = mock_module = _get_mock_module(config)
|
||||
self.patch = self._Patcher(self._patches, self._mocks, mock_module)
|
||||
# aliases for convenience
|
||||
self.Mock = mock_module.Mock
|
||||
self.MagicMock = mock_module.MagicMock
|
||||
self.NonCallableMock = mock_module.NonCallableMock
|
||||
self.PropertyMock = mock_module.PropertyMock
|
||||
self.call = mock_module.call
|
||||
self.ANY = mock_module.ANY
|
||||
self.DEFAULT = mock_module.DEFAULT
|
||||
self.create_autospec = mock_module.create_autospec
|
||||
self.sentinel = mock_module.sentinel
|
||||
self.mock_open = mock_module.mock_open
|
||||
|
||||
def resetall(self):
|
||||
"""
|
||||
Call reset_mock() on all patchers started by this fixture.
|
||||
"""
|
||||
for m in self._mocks:
|
||||
m.reset_mock()
|
||||
|
||||
def stopall(self):
|
||||
"""
|
||||
Stop all patchers started by this fixture. Can be safely called multiple
|
||||
times.
|
||||
"""
|
||||
for p in reversed(self._patches):
|
||||
p.stop()
|
||||
self._patches[:] = []
|
||||
self._mocks[:] = []
|
||||
|
||||
def spy(self, obj, name):
|
||||
"""
|
||||
Creates a spy of method. It will run method normally, but it is now
|
||||
possible to use `mock` call features with it, like call count.
|
||||
|
||||
:param object obj: An object.
|
||||
:param unicode name: A method in object.
|
||||
:rtype: mock.MagicMock
|
||||
:return: Spy object.
|
||||
"""
|
||||
method = getattr(obj, name)
|
||||
|
||||
autospec = inspect.ismethod(method) or inspect.isfunction(method)
|
||||
# Can't use autospec classmethod or staticmethod objects
|
||||
# see: https://bugs.python.org/issue23078
|
||||
if inspect.isclass(obj):
|
||||
# Bypass class descriptor:
|
||||
# http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
|
||||
try:
|
||||
value = obj.__getattribute__(obj, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(value, (classmethod, staticmethod)):
|
||||
autospec = False
|
||||
|
||||
result = self.patch.object(obj, name, side_effect=method, autospec=autospec)
|
||||
return result
|
||||
|
||||
def stub(self, name=None):
|
||||
"""
|
||||
Creates a stub method. It accepts any arguments. Ideal to register to
|
||||
callbacks in tests.
|
||||
|
||||
:param name: the constructed stub's name as used in repr
|
||||
:rtype: mock.MagicMock
|
||||
:return: Stub object.
|
||||
"""
|
||||
return self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name)
|
||||
|
||||
class _Patcher(object):
|
||||
"""
|
||||
Object to provide the same interface as mock.patch, mock.patch.object,
|
||||
etc. We need this indirection to keep the same API of the mock package.
|
||||
"""
|
||||
|
||||
def __init__(self, patches, mocks, mock_module):
|
||||
self._patches = patches
|
||||
self._mocks = mocks
|
||||
self.mock_module = mock_module
|
||||
|
||||
def _start_patch(self, mock_func, *args, **kwargs):
|
||||
"""Patches something by calling the given function from the mock
|
||||
module, registering the patch to stop it later and returns the
|
||||
mock object resulting from the mock call.
|
||||
"""
|
||||
p = mock_func(*args, **kwargs)
|
||||
mocked = p.start()
|
||||
self._patches.append(p)
|
||||
if hasattr(mocked, "reset_mock"):
|
||||
self._mocks.append(mocked)
|
||||
return mocked
|
||||
|
||||
def object(self, *args, **kwargs):
|
||||
"""API to mock.patch.object"""
|
||||
return self._start_patch(self.mock_module.patch.object, *args, **kwargs)
|
||||
|
||||
def multiple(self, *args, **kwargs):
|
||||
"""API to mock.patch.multiple"""
|
||||
return self._start_patch(self.mock_module.patch.multiple, *args, **kwargs)
|
||||
|
||||
def dict(self, *args, **kwargs):
|
||||
"""API to mock.patch.dict"""
|
||||
return self._start_patch(self.mock_module.patch.dict, *args, **kwargs)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""API to mock.patch"""
|
||||
return self._start_patch(self.mock_module.patch, *args, **kwargs)
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
def mocker(pytestconfig):
|
||||
"""
|
||||
return an object that has the same interface to the `mock` module, but
|
||||
takes care of automatically undoing all patches after each test method.
|
||||
"""
|
||||
result = MockFixture(pytestconfig)
|
||||
yield result
|
||||
result.stopall()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock(mocker):
|
||||
"""
|
||||
Same as "mocker", but kept only for backward compatibility.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
'"mock" fixture has been deprecated, use "mocker" instead', DeprecationWarning
|
||||
)
|
||||
return mocker
|
||||
|
||||
|
||||
_mock_module_patches = []
|
||||
_mock_module_originals = {}
|
||||
|
||||
|
||||
def assert_wrapper(__wrapped_mock_method__, *args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
try:
|
||||
__wrapped_mock_method__(*args, **kwargs)
|
||||
return
|
||||
except AssertionError as e:
|
||||
if getattr(e, "_mock_introspection_applied", 0):
|
||||
msg = text_type(e)
|
||||
else:
|
||||
__mock_self = args[0]
|
||||
msg = text_type(e)
|
||||
if __mock_self.call_args is not None:
|
||||
actual_args, actual_kwargs = __mock_self.call_args
|
||||
msg += "\n\npytest introspection follows:\n"
|
||||
try:
|
||||
assert actual_args == args[1:]
|
||||
except AssertionError as e:
|
||||
msg += "\nArgs:\n" + text_type(e)
|
||||
try:
|
||||
assert actual_kwargs == kwargs
|
||||
except AssertionError as e:
|
||||
msg += "\nKwargs:\n" + text_type(e)
|
||||
e = AssertionError(msg)
|
||||
e._mock_introspection_applied = True
|
||||
raise e
|
||||
|
||||
|
||||
def wrap_assert_not_called(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_with(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_once(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_once"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_once_with(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_once_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_has_calls(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_any_call(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_any_call"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_methods(config):
|
||||
"""
|
||||
Wrap assert methods of mock module so we can hide their traceback and
|
||||
add introspection information to specified argument asserts.
|
||||
"""
|
||||
# Make sure we only do this once
|
||||
if _mock_module_originals:
|
||||
return
|
||||
|
||||
mock_module = _get_mock_module(config)
|
||||
|
||||
wrappers = {
|
||||
"assert_called": wrap_assert_called,
|
||||
"assert_called_once": wrap_assert_called_once,
|
||||
"assert_called_with": wrap_assert_called_with,
|
||||
"assert_called_once_with": wrap_assert_called_once_with,
|
||||
"assert_any_call": wrap_assert_any_call,
|
||||
"assert_has_calls": wrap_assert_has_calls,
|
||||
"assert_not_called": wrap_assert_not_called,
|
||||
}
|
||||
for method, wrapper in wrappers.items():
|
||||
try:
|
||||
original = getattr(mock_module.NonCallableMock, method)
|
||||
except AttributeError: # pragma: no cover
|
||||
continue
|
||||
_mock_module_originals[method] = original
|
||||
patcher = mock_module.patch.object(mock_module.NonCallableMock, method, wrapper)
|
||||
patcher.start()
|
||||
_mock_module_patches.append(patcher)
|
||||
|
||||
if hasattr(config, "add_cleanup"):
|
||||
add_cleanup = config.add_cleanup
|
||||
else:
|
||||
# pytest 2.7 compatibility
|
||||
add_cleanup = config._cleanup.append
|
||||
add_cleanup(unwrap_assert_methods)
|
||||
|
||||
|
||||
def unwrap_assert_methods():
|
||||
for patcher in _mock_module_patches:
|
||||
try:
|
||||
patcher.stop()
|
||||
except RuntimeError as e:
|
||||
# a patcher might have been stopped by user code (#137)
|
||||
# so we need to catch this error here and ignore it;
|
||||
# unfortunately there's no public API to check if a patch
|
||||
# has been started, so catching the error it is
|
||||
if text_type(e) == "stop called on unstarted patcher":
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
_mock_module_patches[:] = []
|
||||
_mock_module_originals.clear()
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addini(
|
||||
"mock_traceback_monkeypatch",
|
||||
"Monkeypatch the mock library to improve reporting of the "
|
||||
"assert_called_... methods",
|
||||
default=True,
|
||||
)
|
||||
parser.addini(
|
||||
"mock_use_standalone_module",
|
||||
'Use standalone "mock" (from PyPI) instead of builtin "unittest.mock" '
|
||||
"on Python 3",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
def parse_ini_boolean(value):
|
||||
if value in (True, False):
|
||||
return value
|
||||
try:
|
||||
return {"true": True, "false": False}[value.lower()]
|
||||
except KeyError:
|
||||
raise ValueError("unknown string for bool: %r" % value)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
tb = config.getoption("--tb", default="auto")
|
||||
if (
|
||||
parse_ini_boolean(config.getini("mock_traceback_monkeypatch"))
|
||||
and tb != "native"
|
||||
):
|
||||
wrap_assert_methods(config)
|
|
@ -1,6 +1,3 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
|
23
setup.py
23
setup.py
|
@ -1,38 +1,41 @@
|
|||
from io import open
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="pytest-mock",
|
||||
entry_points={"pytest11": ["pytest_mock = pytest_mock"]},
|
||||
py_modules=["pytest_mock", "_pytest_mock_version"],
|
||||
packages=find_packages(where="src"),
|
||||
package_dir={"": "src"},
|
||||
platforms="any",
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
install_requires=["pytest>=2.7", 'mock;python_version<"3.0"'],
|
||||
use_scm_version={"write_to": "_pytest_mock_version.py"},
|
||||
package_data={
|
||||
"pytest_mock": ["py.typed"],
|
||||
},
|
||||
python_requires=">=3.6",
|
||||
install_requires=["pytest>=5.0"],
|
||||
use_scm_version={"write_to": "src/pytest_mock/_version.py"},
|
||||
setup_requires=["setuptools_scm"],
|
||||
url="https://github.com/pytest-dev/pytest-mock/",
|
||||
license="MIT",
|
||||
author="Bruno Oliveira",
|
||||
author_email="nicoddemus@gmail.com",
|
||||
description="Thin-wrapper around the mock package for easier use with py.test",
|
||||
description="Thin-wrapper around the mock package for easier use with pytest",
|
||||
long_description=open("README.rst", encoding="utf-8").read(),
|
||||
keywords="pytest mock",
|
||||
extras_require={"dev": ["pre-commit", "tox"]},
|
||||
extras_require={"dev": ["pre-commit", "tox", "pytest-asyncio"]},
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Framework :: Pytest",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Testing",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: pytest-mock
|
||||
Version: 1.10.4
|
||||
Summary: Thin-wrapper around the mock package for easier use with py.test
|
||||
Version: 3.6.1
|
||||
Summary: Thin-wrapper around the mock package for easier use with pytest
|
||||
Home-page: https://github.com/pytest-dev/pytest-mock/
|
||||
Author: Bruno Oliveira
|
||||
Author-email: nicoddemus@gmail.com
|
||||
|
@ -10,10 +10,8 @@ Description: ===========
|
|||
pytest-mock
|
||||
===========
|
||||
|
||||
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
|
||||
provided by the `mock package <http://pypi.python.org/pypi/mock>`_,
|
||||
but with the benefit of not having to worry about undoing patches at the end
|
||||
of a test:
|
||||
This 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
|
||||
|
||||
|
@ -31,8 +29,11 @@ Description: ===========
|
|||
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.
|
||||
|
||||
|python| |version| |anaconda| |ci| |appveyor| |coverage| |black|
|
||||
|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit|
|
||||
|
||||
.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock
|
||||
|
@ -40,14 +41,11 @@ Description: ===========
|
|||
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest-mock
|
||||
|
||||
.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.svg
|
||||
:target: https://travis-ci.org/pytest-dev/pytest-mock
|
||||
.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest-mock/actions
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest-mock
|
||||
|
||||
.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest-mock
|
||||
.. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/pytest-dev/pytest-mock?branch=master
|
||||
|
||||
.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg
|
||||
:target: https://pypi.python.org/pypi/pytest-mock/
|
||||
|
@ -55,6 +53,9 @@ Description: ===========
|
|||
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
|
||||
.. |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
|
||||
|
||||
`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>`_
|
||||
|
||||
Usage
|
||||
|
@ -81,7 +82,7 @@ Description: ===========
|
|||
* `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.
|
||||
|
||||
These objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
|
||||
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>`_
|
||||
|
@ -91,33 +92,93 @@ Description: ===========
|
|||
* `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 spy acts exactly like the original method in all cases, except it allows use of `mock`
|
||||
features with it, like retrieving call count. It also works for class and static methods.
|
||||
|
||||
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(mocker):
|
||||
def test_spy_method(mocker):
|
||||
class Foo(object):
|
||||
def bar(self):
|
||||
return 42
|
||||
def bar(self, v):
|
||||
return v * 2
|
||||
|
||||
foo = Foo()
|
||||
mocker.spy(foo, 'bar')
|
||||
assert foo.bar() == 42
|
||||
assert foo.bar.call_count == 1
|
||||
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, for instance.
|
||||
May be passed a name to be used by the constructed stub object in its repr (useful for debugging).
|
||||
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
|
||||
|
||||
|
@ -134,18 +195,17 @@ Description: ===========
|
|||
Improved reporting of mock call assertion errors
|
||||
------------------------------------------------
|
||||
|
||||
|
||||
This plugin monkeypatches the mock library to improve pytest output for failures
|
||||
of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback
|
||||
entries from the ``mock`` module.
|
||||
|
||||
It also adds introspection information on differing call arguments when
|
||||
calling the helper methods. This features catches `AssertionError` raised in
|
||||
the method, and uses py.test's own `advanced assertions`_ to return a better
|
||||
the method, and uses pytest's own `advanced assertions`_ to return a better
|
||||
diff::
|
||||
|
||||
|
||||
mocker = <pytest_mock.MockFixture object at 0x0381E2D0>
|
||||
mocker = <pytest_mock.MockerFixture object at 0x0381E2D0>
|
||||
|
||||
def test(mocker):
|
||||
m = mocker.Mock()
|
||||
|
@ -186,7 +246,7 @@ Description: ===========
|
|||
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://pytest.org/latest/assert.html
|
||||
.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html
|
||||
|
||||
|
||||
Use standalone "mock" package
|
||||
|
@ -206,13 +266,25 @@ Description: ===========
|
|||
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
|
||||
-----------------------------------
|
||||
|
||||
Requirements
|
||||
============
|
||||
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:
|
||||
|
||||
* Python 2.7, Python 3.4+
|
||||
* pytest
|
||||
* mock (for Python 2)
|
||||
.. 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
|
||||
|
@ -285,47 +357,29 @@ Description: ===========
|
|||
naming fixtures as parameters, or ``pytest.mark.parametrize``;
|
||||
- you can't easily undo the mocking during the test execution;
|
||||
|
||||
|
||||
**Note about usage as context manager**
|
||||
|
||||
Although mocker's API is intentionally the same as ``mock.patch``'s, its use
|
||||
as context manager and function decorator is **not** supported through the
|
||||
fixture. The purpose of this plugin is to make the use of context managers and
|
||||
function decorators for mocking unnecessary. Indeed, trying to use the
|
||||
functionality in ``mocker`` in this manner can lead to non-intuitive errors:
|
||||
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
|
||||
|
||||
def test_context_manager(mocker):
|
||||
a = A()
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
assert a.doIt() == True
|
||||
import contextlib
|
||||
import mock
|
||||
|
||||
.. code-block:: console
|
||||
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')
|
||||
|
||||
================================== FAILURES ===================================
|
||||
____________________________ test_context_manager _____________________________
|
||||
in test_context_manager
|
||||
with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):
|
||||
E AttributeError: __exit__
|
||||
stack.enter_context(mock.patch('os.listdir'))
|
||||
assert UnixFS.ls('dir') == expected
|
||||
# ...
|
||||
|
||||
You can however use ``mocker.mock_module`` to access the underlying ``mock``
|
||||
module, e.g. to return a context manager in a fixture that mocks something
|
||||
temporarily:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def fixture_cm(mocker):
|
||||
@contextlib.contextmanager
|
||||
def my_cm():
|
||||
def mocked():
|
||||
pass
|
||||
|
||||
with mocker.mock_module.patch.object(SomeClass, 'method', mocked):
|
||||
yield
|
||||
return my_cm
|
||||
stack.enter_context(mock.patch('shutil.copy'))
|
||||
UnixFS.cp('src', 'dst')
|
||||
# ...
|
||||
|
||||
But this is arguably a little more complex than using ``pytest-mock``.
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
@ -342,7 +396,7 @@ Description: ===========
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ tox -e py27,py36,linting
|
||||
$ tox -e py38,linting
|
||||
|
||||
Style checks and formatting are done automatically during commit courtesy of
|
||||
`pre-commit <https://pre-commit.com>`_.
|
||||
|
@ -352,6 +406,11 @@ Description: ===========
|
|||
|
||||
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
|
||||
|
||||
Keywords: pytest mock
|
||||
|
@ -361,14 +420,12 @@ Classifier: Framework :: Pytest
|
|||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=3.6
|
||||
Provides-Extra: dev
|
23
src/pytest_mock.egg-info/SOURCES.txt
Normal file
23
src/pytest_mock.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
.gitignore
|
||||
.pre-commit-config.yaml
|
||||
CHANGELOG.rst
|
||||
HOWTORELEASE.rst
|
||||
LICENSE
|
||||
README.rst
|
||||
mypy.ini
|
||||
setup.py
|
||||
tox.ini
|
||||
.github/FUNDING.yml
|
||||
.github/workflows/main.yml
|
||||
src/pytest_mock/__init__.py
|
||||
src/pytest_mock/_util.py
|
||||
src/pytest_mock/_version.py
|
||||
src/pytest_mock/plugin.py
|
||||
src/pytest_mock/py.typed
|
||||
src/pytest_mock.egg-info/PKG-INFO
|
||||
src/pytest_mock.egg-info/SOURCES.txt
|
||||
src/pytest_mock.egg-info/dependency_links.txt
|
||||
src/pytest_mock.egg-info/entry_points.txt
|
||||
src/pytest_mock.egg-info/requires.txt
|
||||
src/pytest_mock.egg-info/top_level.txt
|
||||
tests/test_pytest_mock.py
|
6
src/pytest_mock.egg-info/requires.txt
Normal file
6
src/pytest_mock.egg-info/requires.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
pytest>=5.0
|
||||
|
||||
[dev]
|
||||
pre-commit
|
||||
tox
|
||||
pytest-asyncio
|
1
src/pytest_mock.egg-info/top_level.txt
Normal file
1
src/pytest_mock.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
pytest_mock
|
24
src/pytest_mock/__init__.py
Normal file
24
src/pytest_mock/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from pytest_mock.plugin import class_mocker
|
||||
from pytest_mock.plugin import mocker
|
||||
from pytest_mock.plugin import MockerFixture
|
||||
from pytest_mock.plugin import module_mocker
|
||||
from pytest_mock.plugin import package_mocker
|
||||
from pytest_mock.plugin import pytest_addoption
|
||||
from pytest_mock.plugin import pytest_configure
|
||||
from pytest_mock.plugin import PytestMockWarning
|
||||
from pytest_mock.plugin import session_mocker
|
||||
|
||||
MockFixture = MockerFixture # backward-compatibility only (#204)
|
||||
|
||||
__all__ = [
|
||||
"MockerFixture",
|
||||
"MockFixture",
|
||||
"PytestMockWarning",
|
||||
"pytest_addoption",
|
||||
"pytest_configure",
|
||||
"session_mocker",
|
||||
"package_mocker",
|
||||
"module_mocker",
|
||||
"class_mocker",
|
||||
"mocker",
|
||||
]
|
36
src/pytest_mock/_util.py
Normal file
36
src/pytest_mock/_util.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from typing import Union
|
||||
|
||||
_mock_module = None
|
||||
|
||||
|
||||
def get_mock_module(config):
|
||||
"""
|
||||
Import and return the actual "mock" module. By default this is
|
||||
"unittest.mock", but the user can force to always use "mock" using
|
||||
the mock_use_standalone_module ini option.
|
||||
"""
|
||||
global _mock_module
|
||||
if _mock_module is None:
|
||||
use_standalone_module = parse_ini_boolean(
|
||||
config.getini("mock_use_standalone_module")
|
||||
)
|
||||
if use_standalone_module:
|
||||
import mock
|
||||
|
||||
_mock_module = mock
|
||||
else:
|
||||
import unittest.mock
|
||||
|
||||
_mock_module = unittest.mock
|
||||
|
||||
return _mock_module
|
||||
|
||||
|
||||
def parse_ini_boolean(value: Union[bool, str]) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value.lower() == "true":
|
||||
return True
|
||||
if value.lower() == "false":
|
||||
return False
|
||||
raise ValueError("unknown string for bool: %r" % value)
|
|
@ -1,4 +1,5 @@
|
|||
# coding: utf-8
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
version = '1.10.4'
|
||||
version = '3.6.1'
|
||||
version_tuple = (3, 6, 1)
|
601
src/pytest_mock/plugin.py
Normal file
601
src/pytest_mock/plugin.py
Normal file
|
@ -0,0 +1,601 @@
|
|||
import asyncio
|
||||
import builtins
|
||||
import functools
|
||||
import inspect
|
||||
import sys
|
||||
import unittest.mock
|
||||
import warnings
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
from ._util import get_mock_module
|
||||
from ._util import parse_ini_boolean
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class PytestMockWarning(UserWarning):
|
||||
"""Base class for all warnings emitted by pytest-mock."""
|
||||
|
||||
|
||||
class MockerFixture:
|
||||
"""
|
||||
Fixture that provides the same interface to functions in the mock module,
|
||||
ensuring that they are uninstalled at the end of each test.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Any) -> None:
|
||||
self._patches = [] # type: List[Any]
|
||||
self._mocks = [] # type: List[Any]
|
||||
self.mock_module = mock_module = get_mock_module(config)
|
||||
self.patch = self._Patcher(
|
||||
self._patches, self._mocks, mock_module
|
||||
) # type: MockerFixture._Patcher
|
||||
# aliases for convenience
|
||||
self.Mock = mock_module.Mock
|
||||
self.MagicMock = mock_module.MagicMock
|
||||
self.NonCallableMock = mock_module.NonCallableMock
|
||||
self.PropertyMock = mock_module.PropertyMock
|
||||
if hasattr(mock_module, "AsyncMock"):
|
||||
self.AsyncMock = mock_module.AsyncMock
|
||||
self.call = mock_module.call
|
||||
self.ANY = mock_module.ANY
|
||||
self.DEFAULT = mock_module.DEFAULT
|
||||
self.create_autospec = mock_module.create_autospec
|
||||
self.sentinel = mock_module.sentinel
|
||||
self.mock_open = mock_module.mock_open
|
||||
if hasattr(mock_module, "seal"):
|
||||
self.seal = mock_module.seal
|
||||
|
||||
def resetall(
|
||||
self, *, return_value: bool = False, side_effect: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Call reset_mock() on all patchers started by this fixture.
|
||||
|
||||
:param bool return_value: Reset the return_value of mocks.
|
||||
:param bool side_effect: Reset the side_effect of mocks.
|
||||
"""
|
||||
supports_reset_mock_with_args: Tuple[Type[Any], ...]
|
||||
if hasattr(self, "AsyncMock"):
|
||||
supports_reset_mock_with_args = (self.Mock, self.AsyncMock)
|
||||
else:
|
||||
supports_reset_mock_with_args = (self.Mock,)
|
||||
|
||||
for m in self._mocks:
|
||||
# See issue #237.
|
||||
if isinstance(m, supports_reset_mock_with_args):
|
||||
m.reset_mock(return_value=return_value, side_effect=side_effect)
|
||||
else:
|
||||
m.reset_mock()
|
||||
|
||||
def stopall(self) -> None:
|
||||
"""
|
||||
Stop all patchers started by this fixture. Can be safely called multiple
|
||||
times.
|
||||
"""
|
||||
for p in reversed(self._patches):
|
||||
p.stop()
|
||||
self._patches[:] = []
|
||||
self._mocks[:] = []
|
||||
|
||||
def spy(self, obj: object, name: str) -> unittest.mock.MagicMock:
|
||||
"""
|
||||
Create a spy of method. It will run method normally, but it is now
|
||||
possible to use `mock` call features with it, like call count.
|
||||
|
||||
:param obj: An object.
|
||||
:param name: A method in object.
|
||||
:return: Spy object.
|
||||
"""
|
||||
method = getattr(obj, name)
|
||||
if inspect.isclass(obj) and isinstance(
|
||||
inspect.getattr_static(obj, name), (classmethod, staticmethod)
|
||||
):
|
||||
# Can't use autospec classmethod or staticmethod objects before 3.7
|
||||
# see: https://bugs.python.org/issue23078
|
||||
autospec = False
|
||||
else:
|
||||
autospec = inspect.ismethod(method) or inspect.isfunction(method)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
spy_obj.spy_return = None
|
||||
spy_obj.spy_exception = None
|
||||
try:
|
||||
r = method(*args, **kwargs)
|
||||
except BaseException as e:
|
||||
spy_obj.spy_exception = e
|
||||
raise
|
||||
else:
|
||||
spy_obj.spy_return = r
|
||||
return r
|
||||
|
||||
async def async_wrapper(*args, **kwargs):
|
||||
spy_obj.spy_return = None
|
||||
spy_obj.spy_exception = None
|
||||
try:
|
||||
r = await method(*args, **kwargs)
|
||||
except BaseException as e:
|
||||
spy_obj.spy_exception = e
|
||||
raise
|
||||
else:
|
||||
spy_obj.spy_return = r
|
||||
return r
|
||||
|
||||
if asyncio.iscoroutinefunction(method):
|
||||
wrapped = functools.update_wrapper(async_wrapper, method)
|
||||
else:
|
||||
wrapped = functools.update_wrapper(wrapper, method)
|
||||
|
||||
spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec)
|
||||
spy_obj.spy_return = None
|
||||
spy_obj.spy_exception = None
|
||||
return spy_obj
|
||||
|
||||
def stub(self, name: Optional[str] = None) -> unittest.mock.MagicMock:
|
||||
"""
|
||||
Create a 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(
|
||||
unittest.mock.MagicMock,
|
||||
self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name),
|
||||
)
|
||||
|
||||
class _Patcher:
|
||||
"""
|
||||
Object to provide the same interface as mock.patch, mock.patch.object,
|
||||
etc. We need this indirection to keep the same API of the mock package.
|
||||
"""
|
||||
|
||||
DEFAULT = object()
|
||||
|
||||
def __init__(self, patches, mocks, mock_module):
|
||||
self._patches = patches
|
||||
self._mocks = mocks
|
||||
self.mock_module = mock_module
|
||||
|
||||
def _start_patch(
|
||||
self, mock_func: Any, warn_on_mock_enter: bool, *args: Any, **kwargs: Any
|
||||
) -> unittest.mock.MagicMock:
|
||||
"""Patches something by calling the given function from the mock
|
||||
module, registering the patch to stop it later and returns the
|
||||
mock object resulting from the mock call.
|
||||
"""
|
||||
p = mock_func(*args, **kwargs)
|
||||
mocked = p.start() # type: unittest.mock.MagicMock
|
||||
self._patches.append(p)
|
||||
if hasattr(mocked, "reset_mock"):
|
||||
self._mocks.append(mocked)
|
||||
# check if `mocked` is actually a mock object, as depending on autospec or target
|
||||
# parameters `mocked` can be anything
|
||||
if hasattr(mocked, "__enter__") and warn_on_mock_enter:
|
||||
if sys.version_info >= (3, 8):
|
||||
depth = 5
|
||||
else:
|
||||
depth = 4
|
||||
mocked.__enter__.side_effect = lambda: warnings.warn(
|
||||
"Mocks returned by pytest-mock do not need to be used as context managers. "
|
||||
"The mocker fixture automatically undoes mocking at the end of a test. "
|
||||
"This warning can be ignored if it was triggered by mocking a context manager. "
|
||||
"https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager",
|
||||
PytestMockWarning,
|
||||
stacklevel=depth,
|
||||
)
|
||||
return mocked
|
||||
|
||||
def object(
|
||||
self,
|
||||
target: object,
|
||||
attribute: str,
|
||||
new: object = DEFAULT,
|
||||
spec: Optional[object] = None,
|
||||
create: bool = False,
|
||||
spec_set: Optional[object] = None,
|
||||
autospec: Optional[object] = None,
|
||||
new_callable: object = None,
|
||||
**kwargs: Any
|
||||
) -> unittest.mock.MagicMock:
|
||||
"""API to mock.patch.object"""
|
||||
if new is self.DEFAULT:
|
||||
new = self.mock_module.DEFAULT
|
||||
return self._start_patch(
|
||||
self.mock_module.patch.object,
|
||||
True,
|
||||
target,
|
||||
attribute,
|
||||
new=new,
|
||||
spec=spec,
|
||||
create=create,
|
||||
spec_set=spec_set,
|
||||
autospec=autospec,
|
||||
new_callable=new_callable,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def context_manager(
|
||||
self,
|
||||
target: builtins.object,
|
||||
attribute: str,
|
||||
new: builtins.object = DEFAULT,
|
||||
spec: Optional[builtins.object] = None,
|
||||
create: bool = False,
|
||||
spec_set: Optional[builtins.object] = None,
|
||||
autospec: Optional[builtins.object] = None,
|
||||
new_callable: builtins.object = None,
|
||||
**kwargs: Any
|
||||
) -> unittest.mock.MagicMock:
|
||||
"""This is equivalent to mock.patch.object except that the returned mock
|
||||
does not issue a warning when used as a context manager."""
|
||||
if new is self.DEFAULT:
|
||||
new = self.mock_module.DEFAULT
|
||||
return self._start_patch(
|
||||
self.mock_module.patch.object,
|
||||
False,
|
||||
target,
|
||||
attribute,
|
||||
new=new,
|
||||
spec=spec,
|
||||
create=create,
|
||||
spec_set=spec_set,
|
||||
autospec=autospec,
|
||||
new_callable=new_callable,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def multiple(
|
||||
self,
|
||||
target: builtins.object,
|
||||
spec: Optional[builtins.object] = None,
|
||||
create: bool = False,
|
||||
spec_set: Optional[builtins.object] = None,
|
||||
autospec: Optional[builtins.object] = None,
|
||||
new_callable: Optional[builtins.object] = None,
|
||||
**kwargs: Any
|
||||
) -> Dict[str, unittest.mock.MagicMock]:
|
||||
"""API to mock.patch.multiple"""
|
||||
return self._start_patch(
|
||||
self.mock_module.patch.multiple,
|
||||
True,
|
||||
target,
|
||||
spec=spec,
|
||||
create=create,
|
||||
spec_set=spec_set,
|
||||
autospec=autospec,
|
||||
new_callable=new_callable,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def dict(
|
||||
self,
|
||||
in_dict: Union[Mapping[Any, Any], str],
|
||||
values: Union[Mapping[Any, Any], Iterable[Tuple[Any, Any]]] = (),
|
||||
clear: bool = False,
|
||||
**kwargs: Any
|
||||
) -> Any:
|
||||
"""API to mock.patch.dict"""
|
||||
return self._start_patch(
|
||||
self.mock_module.patch.dict,
|
||||
True,
|
||||
in_dict,
|
||||
values=values,
|
||||
clear=clear,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@overload
|
||||
def __call__(
|
||||
self,
|
||||
target: str,
|
||||
new: None = ...,
|
||||
spec: Optional[builtins.object] = ...,
|
||||
create: bool = ...,
|
||||
spec_set: Optional[builtins.object] = ...,
|
||||
autospec: Optional[builtins.object] = ...,
|
||||
new_callable: None = ...,
|
||||
**kwargs: Any
|
||||
) -> unittest.mock.MagicMock:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(
|
||||
self,
|
||||
target: str,
|
||||
new: _T,
|
||||
spec: Optional[builtins.object] = ...,
|
||||
create: bool = ...,
|
||||
spec_set: Optional[builtins.object] = ...,
|
||||
autospec: Optional[builtins.object] = ...,
|
||||
new_callable: None = ...,
|
||||
**kwargs: Any
|
||||
) -> _T:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(
|
||||
self,
|
||||
target: str,
|
||||
new: None,
|
||||
spec: Optional[builtins.object],
|
||||
create: bool,
|
||||
spec_set: Optional[builtins.object],
|
||||
autospec: Optional[builtins.object],
|
||||
new_callable: Callable[[], _T],
|
||||
**kwargs: Any
|
||||
) -> _T:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(
|
||||
self,
|
||||
target: str,
|
||||
new: None = ...,
|
||||
spec: Optional[builtins.object] = ...,
|
||||
create: bool = ...,
|
||||
spec_set: Optional[builtins.object] = ...,
|
||||
autospec: Optional[builtins.object] = ...,
|
||||
*,
|
||||
new_callable: Callable[[], _T],
|
||||
**kwargs: Any
|
||||
) -> _T:
|
||||
...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
target: str,
|
||||
new: builtins.object = DEFAULT,
|
||||
spec: Optional[builtins.object] = None,
|
||||
create: bool = False,
|
||||
spec_set: Optional[builtins.object] = None,
|
||||
autospec: Optional[builtins.object] = None,
|
||||
new_callable: Optional[Callable[[], Any]] = None,
|
||||
**kwargs: Any
|
||||
) -> Any:
|
||||
"""API to mock.patch"""
|
||||
if new is self.DEFAULT:
|
||||
new = self.mock_module.DEFAULT
|
||||
return self._start_patch(
|
||||
self.mock_module.patch,
|
||||
True,
|
||||
target,
|
||||
new=new,
|
||||
spec=spec,
|
||||
create=create,
|
||||
spec_set=spec_set,
|
||||
autospec=autospec,
|
||||
new_callable=new_callable,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def _mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
|
||||
"""
|
||||
Return an object that has the same interface to the `mock` module, but
|
||||
takes care of automatically undoing all patches after each test method.
|
||||
"""
|
||||
result = MockerFixture(pytestconfig)
|
||||
yield result
|
||||
result.stopall()
|
||||
|
||||
|
||||
mocker = pytest.fixture()(_mocker) # default scope is function
|
||||
class_mocker = pytest.fixture(scope="class")(_mocker)
|
||||
module_mocker = pytest.fixture(scope="module")(_mocker)
|
||||
package_mocker = pytest.fixture(scope="package")(_mocker)
|
||||
session_mocker = pytest.fixture(scope="session")(_mocker)
|
||||
|
||||
|
||||
_mock_module_patches = [] # type: List[Any]
|
||||
_mock_module_originals = {} # type: Dict[str, Any]
|
||||
|
||||
|
||||
def assert_wrapper(
|
||||
__wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any
|
||||
) -> None:
|
||||
__tracebackhide__ = True
|
||||
try:
|
||||
__wrapped_mock_method__(*args, **kwargs)
|
||||
return
|
||||
except AssertionError as e:
|
||||
if getattr(e, "_mock_introspection_applied", 0):
|
||||
msg = str(e)
|
||||
else:
|
||||
__mock_self = args[0]
|
||||
msg = str(e)
|
||||
if __mock_self.call_args is not None:
|
||||
actual_args, actual_kwargs = __mock_self.call_args
|
||||
introspection = ""
|
||||
try:
|
||||
assert actual_args == args[1:]
|
||||
except AssertionError as e_args:
|
||||
introspection += "\nArgs:\n" + str(e_args)
|
||||
try:
|
||||
assert actual_kwargs == kwargs
|
||||
except AssertionError as e_kwargs:
|
||||
introspection += "\nKwargs:\n" + str(e_kwargs)
|
||||
if introspection:
|
||||
msg += "\n\npytest introspection follows:\n" + introspection
|
||||
e = AssertionError(msg)
|
||||
e._mock_introspection_applied = True # type:ignore[attr-defined]
|
||||
raise e
|
||||
|
||||
|
||||
def wrap_assert_not_called(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_with(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_once(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_once"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called_once_with(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called_once_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_has_calls(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_any_call(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_any_call"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_called(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_called"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_not_awaited(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_not_awaited"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_awaited_with(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_awaited_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_awaited_once(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_awaited_once"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_awaited_once_with(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_awaited_once_with"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_has_awaits(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_has_awaits"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_any_await(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_any_await"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_awaited(*args: Any, **kwargs: Any) -> None:
|
||||
__tracebackhide__ = True
|
||||
assert_wrapper(_mock_module_originals["assert_awaited"], *args, **kwargs)
|
||||
|
||||
|
||||
def wrap_assert_methods(config: Any) -> None:
|
||||
"""
|
||||
Wrap assert methods of mock module so we can hide their traceback and
|
||||
add introspection information to specified argument asserts.
|
||||
"""
|
||||
# Make sure we only do this once
|
||||
if _mock_module_originals:
|
||||
return
|
||||
|
||||
mock_module = get_mock_module(config)
|
||||
|
||||
wrappers = {
|
||||
"assert_called": wrap_assert_called,
|
||||
"assert_called_once": wrap_assert_called_once,
|
||||
"assert_called_with": wrap_assert_called_with,
|
||||
"assert_called_once_with": wrap_assert_called_once_with,
|
||||
"assert_any_call": wrap_assert_any_call,
|
||||
"assert_has_calls": wrap_assert_has_calls,
|
||||
"assert_not_called": wrap_assert_not_called,
|
||||
}
|
||||
for method, wrapper in wrappers.items():
|
||||
try:
|
||||
original = getattr(mock_module.NonCallableMock, method)
|
||||
except AttributeError: # pragma: no cover
|
||||
continue
|
||||
_mock_module_originals[method] = original
|
||||
patcher = mock_module.patch.object(mock_module.NonCallableMock, method, wrapper)
|
||||
patcher.start()
|
||||
_mock_module_patches.append(patcher)
|
||||
|
||||
if hasattr(mock_module, "AsyncMock"):
|
||||
async_wrappers = {
|
||||
"assert_awaited": wrap_assert_awaited,
|
||||
"assert_awaited_once": wrap_assert_awaited_once,
|
||||
"assert_awaited_with": wrap_assert_awaited_with,
|
||||
"assert_awaited_once_with": wrap_assert_awaited_once_with,
|
||||
"assert_any_await": wrap_assert_any_await,
|
||||
"assert_has_awaits": wrap_assert_has_awaits,
|
||||
"assert_not_awaited": wrap_assert_not_awaited,
|
||||
}
|
||||
for method, wrapper in async_wrappers.items():
|
||||
try:
|
||||
original = getattr(mock_module.AsyncMock, method)
|
||||
except AttributeError: # pragma: no cover
|
||||
continue
|
||||
_mock_module_originals[method] = original
|
||||
patcher = mock_module.patch.object(mock_module.AsyncMock, method, wrapper)
|
||||
patcher.start()
|
||||
_mock_module_patches.append(patcher)
|
||||
|
||||
config.add_cleanup(unwrap_assert_methods)
|
||||
|
||||
|
||||
def unwrap_assert_methods() -> None:
|
||||
for patcher in _mock_module_patches:
|
||||
try:
|
||||
patcher.stop()
|
||||
except RuntimeError as e:
|
||||
# a patcher might have been stopped by user code (#137)
|
||||
# so we need to catch this error here and ignore it;
|
||||
# unfortunately there's no public API to check if a patch
|
||||
# has been started, so catching the error it is
|
||||
if str(e) == "stop called on unstarted patcher":
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
_mock_module_patches[:] = []
|
||||
_mock_module_originals.clear()
|
||||
|
||||
|
||||
def pytest_addoption(parser: Any) -> None:
|
||||
parser.addini(
|
||||
"mock_traceback_monkeypatch",
|
||||
"Monkeypatch the mock library to improve reporting of the "
|
||||
"assert_called_... methods",
|
||||
default=True,
|
||||
)
|
||||
parser.addini(
|
||||
"mock_use_standalone_module",
|
||||
'Use standalone "mock" (from PyPI) instead of builtin "unittest.mock" '
|
||||
"on Python 3",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config: Any) -> None:
|
||||
tb = config.getoption("--tb", default="auto")
|
||||
if (
|
||||
parse_ini_boolean(config.getini("mock_traceback_monkeypatch"))
|
||||
and tb != "native"
|
||||
):
|
||||
wrap_assert_methods(config)
|
0
src/pytest_mock/py.typed
Normal file
0
src/pytest_mock/py.typed
Normal file
|
@ -1,676 +0,0 @@
|
|||
import os
|
||||
import platform
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
import py.code
|
||||
import pytest
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
# could not make some of the tests work on PyPy, patches are welcome!
|
||||
skip_pypy = pytest.mark.skipif(
|
||||
platform.python_implementation() == "PyPy", reason="could not make work on pypy"
|
||||
)
|
||||
|
||||
# Python 3.8 changed the output formatting (bpo-35500).
|
||||
PY38 = sys.version_info >= (3, 8)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def needs_assert_rewrite(pytestconfig):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
option = pytestconfig.getoption("assertmode")
|
||||
if option != "rewrite":
|
||||
pytest.skip(
|
||||
"this test needs assertion rewrite to work but current option "
|
||||
'is "{}"'.format(option)
|
||||
)
|
||||
|
||||
|
||||
class UnixFS(object):
|
||||
"""
|
||||
Wrapper to os functions to simulate a Unix file system, used for testing
|
||||
the mock fixture.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def rm(cls, filename):
|
||||
os.remove(filename)
|
||||
|
||||
@classmethod
|
||||
def ls(cls, path):
|
||||
return os.listdir(path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def check_unix_fs_mocked(tmpdir, mocker):
|
||||
"""
|
||||
performs a standard test in a UnixFS, assuming that both `os.remove` and
|
||||
`os.listdir` have been mocked previously.
|
||||
"""
|
||||
|
||||
def check(mocked_rm, mocked_ls):
|
||||
assert mocked_rm is os.remove
|
||||
assert mocked_ls is os.listdir
|
||||
|
||||
file_name = tmpdir / "foo.txt"
|
||||
file_name.ensure()
|
||||
|
||||
UnixFS.rm(str(file_name))
|
||||
mocked_rm.assert_called_once_with(str(file_name))
|
||||
assert os.path.isfile(str(file_name))
|
||||
|
||||
mocked_ls.return_value = ["bar.txt"]
|
||||
assert UnixFS.ls(str(tmpdir)) == ["bar.txt"]
|
||||
mocked_ls.assert_called_once_with(str(tmpdir))
|
||||
|
||||
mocker.stopall()
|
||||
|
||||
assert UnixFS.ls(str(tmpdir)) == ["foo.txt"]
|
||||
UnixFS.rm(str(file_name))
|
||||
assert not os.path.isfile(str(file_name))
|
||||
|
||||
return check
|
||||
|
||||
|
||||
def mock_using_patch_object(mocker):
|
||||
return mocker.patch.object(os, "remove"), mocker.patch.object(os, "listdir")
|
||||
|
||||
|
||||
def mock_using_patch(mocker):
|
||||
return mocker.patch("os.remove"), mocker.patch("os.listdir")
|
||||
|
||||
|
||||
def mock_using_patch_multiple(mocker):
|
||||
r = mocker.patch.multiple("os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT)
|
||||
return r["remove"], r["listdir"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_fs", [mock_using_patch_object, mock_using_patch, mock_using_patch_multiple]
|
||||
)
|
||||
def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked):
|
||||
"""
|
||||
Installs mocks into `os` functions and performs a standard testing of
|
||||
mock functionality. We parametrize different mock methods to ensure
|
||||
all (intended, at least) mock API is covered.
|
||||
"""
|
||||
# mock it twice on purpose to ensure we unmock it correctly later
|
||||
mock_fs(mocker)
|
||||
mocked_rm, mocked_ls = mock_fs(mocker)
|
||||
check_unix_fs_mocked(mocked_rm, mocked_ls)
|
||||
mocker.resetall()
|
||||
mocker.stopall()
|
||||
|
||||
|
||||
def test_mock_patch_dict(mocker):
|
||||
"""
|
||||
Testing
|
||||
:param mock:
|
||||
"""
|
||||
x = {"original": 1}
|
||||
mocker.patch.dict(x, values=[("new", 10)], clear=True)
|
||||
assert x == {"new": 10}
|
||||
mocker.stopall()
|
||||
assert x == {"original": 1}
|
||||
|
||||
|
||||
def test_mock_patch_dict_resetall(mocker):
|
||||
"""
|
||||
We can call resetall after patching a dict.
|
||||
:param mock:
|
||||
"""
|
||||
x = {"original": 1}
|
||||
mocker.patch.dict(x, values=[("new", 10)], clear=True)
|
||||
assert x == {"new": 10}
|
||||
mocker.resetall()
|
||||
assert x == {"new": 10}
|
||||
|
||||
|
||||
def test_deprecated_mock(testdir):
|
||||
"""
|
||||
Use backward-compatibility-only mock fixture to ensure complete coverage.
|
||||
"""
|
||||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
import os
|
||||
|
||||
def test(mock, tmpdir):
|
||||
mock.patch("os.listdir", return_value=["mocked"])
|
||||
assert os.listdir(str(tmpdir)) == ["mocked"]
|
||||
mock.stopall()
|
||||
assert os.listdir(str(tmpdir)) == []
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(str(p1))
|
||||
result.stdout.fnmatch_lines(
|
||||
['*DeprecationWarning: "mock" fixture has been deprecated, use "mocker"*']
|
||||
)
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"ANY",
|
||||
"call",
|
||||
"create_autospec",
|
||||
"MagicMock",
|
||||
"Mock",
|
||||
"mock_open",
|
||||
"NonCallableMock",
|
||||
"PropertyMock",
|
||||
"sentinel",
|
||||
],
|
||||
)
|
||||
def test_mocker_aliases(name, pytestconfig):
|
||||
from pytest_mock import _get_mock_module, MockFixture
|
||||
|
||||
mock_module = _get_mock_module(pytestconfig)
|
||||
|
||||
mocker = MockFixture(pytestconfig)
|
||||
assert getattr(mocker, name) is getattr(mock_module, name)
|
||||
|
||||
|
||||
def test_mocker_resetall(mocker):
|
||||
listdir = mocker.patch("os.listdir")
|
||||
open = mocker.patch("os.open")
|
||||
|
||||
listdir("/tmp")
|
||||
open("/tmp/foo.txt")
|
||||
listdir.assert_called_once_with("/tmp")
|
||||
open.assert_called_once_with("/tmp/foo.txt")
|
||||
|
||||
mocker.resetall()
|
||||
|
||||
assert not listdir.called
|
||||
assert not open.called
|
||||
|
||||
|
||||
class TestMockerStub:
|
||||
def test_call(self, mocker):
|
||||
stub = mocker.stub()
|
||||
stub("foo", "bar")
|
||||
stub.assert_called_once_with("foo", "bar")
|
||||
|
||||
def test_repr_with_no_name(self, mocker):
|
||||
stub = mocker.stub()
|
||||
assert "name" not in repr(stub)
|
||||
|
||||
def test_repr_with_name(self, mocker):
|
||||
test_name = "funny walk"
|
||||
stub = mocker.stub(name=test_name)
|
||||
assert "name={0!r}".format(test_name) in repr(stub)
|
||||
|
||||
def __test_failure_message(self, mocker, **kwargs):
|
||||
expected_name = kwargs.get("name") or "mock"
|
||||
if PY38:
|
||||
msg = "expected call not found.\nExpected: {0}()\nActual: not called."
|
||||
else:
|
||||
msg = "Expected call: {0}()\nNot called"
|
||||
expected_message = msg.format(expected_name)
|
||||
stub = mocker.stub(**kwargs)
|
||||
with pytest.raises(AssertionError) as exc_info:
|
||||
stub.assert_called_with()
|
||||
assert str(exc_info.value) == expected_message
|
||||
|
||||
def test_failure_message_with_no_name(self, mocker):
|
||||
self.__test_failure_message(mocker)
|
||||
|
||||
@pytest.mark.parametrize("name", (None, "", "f", "The Castle of aaarrrrggh"))
|
||||
def test_failure_message_with_name(self, mocker, name):
|
||||
self.__test_failure_message(mocker, name=name)
|
||||
|
||||
|
||||
def test_instance_method_spy(mocker):
|
||||
class Foo(object):
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
spy = mocker.spy(foo, "bar")
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_instance_method_by_class_spy(mocker):
|
||||
class Foo(object):
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
||||
assert spy.call_args_list == calls
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_instance_method_by_subclass_spy(mocker):
|
||||
class Base(object):
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
||||
assert spy.call_args_list == calls
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_spy(mocker):
|
||||
class Foo(object):
|
||||
@classmethod
|
||||
def bar(cls, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@skip_pypy
|
||||
@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
|
||||
def test_class_method_subclass_spy(mocker):
|
||||
class Base(object):
|
||||
@classmethod
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_with_metaclass_spy(mocker):
|
||||
class MetaFoo(type):
|
||||
pass
|
||||
|
||||
class Foo(object):
|
||||
|
||||
__metaclass__ = MetaFoo
|
||||
|
||||
@classmethod
|
||||
def bar(cls, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_static_method_spy(mocker):
|
||||
class Foo(object):
|
||||
@staticmethod
|
||||
def bar(arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@skip_pypy
|
||||
@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
|
||||
def test_static_method_subclass_spy(mocker):
|
||||
class Base(object):
|
||||
@staticmethod
|
||||
def bar(arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10)
|
||||
spy.assert_called_once_with(arg=10)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_traceback():
|
||||
"""
|
||||
Assert that this file is at the top of the filtered traceback
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except AssertionError:
|
||||
traceback = py.code.ExceptionInfo().traceback
|
||||
crashentry = traceback.getcrashentry()
|
||||
assert crashentry.path == __file__
|
||||
else:
|
||||
raise AssertionError("DID NOT RAISE")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_argument_introspection(left, right):
|
||||
"""
|
||||
Assert detailed argument introspection is used
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except AssertionError as e:
|
||||
# this may be a bit too assuming, but seems nicer then hard-coding
|
||||
import _pytest.assertion.util as util
|
||||
|
||||
# NOTE: we assert with either verbose or not, depending on how our own
|
||||
# test was run by examining sys.argv
|
||||
verbose = any(a.startswith("-v") for a in sys.argv)
|
||||
expected = "\n ".join(util._compare_eq_iterable(left, right, verbose))
|
||||
assert expected in str(e)
|
||||
else:
|
||||
raise AssertionError("DID NOT RAISE")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info[:2] == (3, 4),
|
||||
reason="assert_not_called not available in Python 3.4",
|
||||
)
|
||||
def test_assert_not_called_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
stub.assert_not_called()
|
||||
stub()
|
||||
with assert_traceback():
|
||||
stub.assert_not_called()
|
||||
|
||||
|
||||
def test_assert_called_with_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_called_with("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_with("bar")
|
||||
|
||||
|
||||
def test_assert_called_once_with_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_called_once_with("foo")
|
||||
stub("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_once_with("foo")
|
||||
|
||||
|
||||
def test_assert_called_once_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
if not hasattr(stub, "assert_called_once"):
|
||||
pytest.skip("assert_called_once not available")
|
||||
stub("foo")
|
||||
stub.assert_called_once()
|
||||
stub("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_once()
|
||||
|
||||
|
||||
def test_assert_called_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
if not hasattr(stub, "assert_called"):
|
||||
pytest.skip("assert_called_once not available")
|
||||
with assert_traceback():
|
||||
stub.assert_called()
|
||||
stub("foo")
|
||||
stub.assert_called()
|
||||
stub("foo")
|
||||
stub.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_assert_called_args_with_introspection(mocker):
|
||||
stub = mocker.stub()
|
||||
|
||||
complex_args = ("a", 1, set(["test"]))
|
||||
wrong_args = ("b", 2, set(["jest"]))
|
||||
|
||||
stub(*complex_args)
|
||||
stub.assert_called_with(*complex_args)
|
||||
stub.assert_called_once_with(*complex_args)
|
||||
|
||||
with assert_argument_introspection(complex_args, wrong_args):
|
||||
stub.assert_called_with(*wrong_args)
|
||||
stub.assert_called_once_with(*wrong_args)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_assert_called_kwargs_with_introspection(mocker):
|
||||
stub = mocker.stub()
|
||||
|
||||
complex_kwargs = dict(foo={"bar": 1, "baz": "spam"})
|
||||
wrong_kwargs = dict(foo={"goo": 1, "baz": "bran"})
|
||||
|
||||
stub(**complex_kwargs)
|
||||
stub.assert_called_with(**complex_kwargs)
|
||||
stub.assert_called_once_with(**complex_kwargs)
|
||||
|
||||
with assert_argument_introspection(complex_kwargs, wrong_kwargs):
|
||||
stub.assert_called_with(**wrong_kwargs)
|
||||
stub.assert_called_once_with(**wrong_kwargs)
|
||||
|
||||
|
||||
def test_assert_any_call_wrapper(mocker):
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub("foo")
|
||||
stub.assert_any_call("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_any_call("bar")
|
||||
|
||||
|
||||
def test_assert_has_calls(mocker):
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_has_calls([mocker.call("foo")])
|
||||
with assert_traceback():
|
||||
stub.assert_has_calls([mocker.call("bar")])
|
||||
|
||||
|
||||
def test_monkeypatch_ini(mocker, testdir):
|
||||
# Make sure the following function actually tests something
|
||||
stub = mocker.stub()
|
||||
assert stub.assert_called_with.__module__ != stub.__module__
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import py.code
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
assert stub.assert_called_with.__module__ == stub.__module__
|
||||
"""
|
||||
)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
mock_traceback_monkeypatch = false
|
||||
"""
|
||||
)
|
||||
result = runpytest_subprocess(testdir)
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_parse_ini_boolean():
|
||||
import pytest_mock
|
||||
|
||||
assert pytest_mock.parse_ini_boolean("True") is True
|
||||
assert pytest_mock.parse_ini_boolean("false") is False
|
||||
with pytest.raises(ValueError):
|
||||
pytest_mock.parse_ini_boolean("foo")
|
||||
|
||||
|
||||
def test_patched_method_parameter_name(mocker):
|
||||
"""Test that our internal code uses uncommon names when wrapping other
|
||||
"mock" methods to avoid conflicts with user code (#31).
|
||||
"""
|
||||
|
||||
class Request:
|
||||
@classmethod
|
||||
def request(cls, method, args):
|
||||
pass
|
||||
|
||||
m = mocker.patch.object(Request, "request")
|
||||
Request.request(method="get", args={"type": "application/json"})
|
||||
m.assert_called_once_with(method="get", args={"type": "application/json"})
|
||||
|
||||
|
||||
def test_monkeypatch_native(testdir):
|
||||
"""Automatically disable monkeypatching when --tb=native.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
stub(1, greet='hello')
|
||||
stub.assert_called_once_with(1, greet='hey')
|
||||
"""
|
||||
)
|
||||
result = runpytest_subprocess(testdir, "--tb=native")
|
||||
assert result.ret == 1
|
||||
assert "During handling of the above exception" not in result.stdout.str()
|
||||
assert "Differing items:" not in result.stdout.str()
|
||||
traceback_lines = [
|
||||
x
|
||||
for x in result.stdout.str().splitlines()
|
||||
if "Traceback (most recent call last)" in x
|
||||
]
|
||||
assert (
|
||||
len(traceback_lines) == 1
|
||||
) # make sure there are no duplicated tracebacks (#44)
|
||||
|
||||
|
||||
def test_monkeypatch_no_terminal(testdir):
|
||||
"""Don't crash without 'terminal' plugin.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
stub(1, greet='hello')
|
||||
stub.assert_called_once_with(1, greet='hey')
|
||||
"""
|
||||
)
|
||||
result = runpytest_subprocess(testdir, "-p", "no:terminal", "-s")
|
||||
assert result.ret == 1
|
||||
assert result.stdout.lines == []
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] < 3, reason="Py3 only")
|
||||
def test_standalone_mock(testdir):
|
||||
"""Check that the "mock_use_standalone" is being used.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
mock_use_standalone_module = true
|
||||
"""
|
||||
)
|
||||
result = runpytest_subprocess(testdir)
|
||||
assert result.ret == 3
|
||||
result.stderr.fnmatch_lines(["*No module named 'mock'*"])
|
||||
|
||||
|
||||
def runpytest_subprocess(testdir, *args):
|
||||
"""Testdir.runpytest_subprocess only available in pytest-2.8+"""
|
||||
if hasattr(testdir, "runpytest_subprocess"):
|
||||
return testdir.runpytest_subprocess(*args)
|
||||
else:
|
||||
# pytest 2.7.X
|
||||
return testdir.runpytest(*args)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_detailed_introspection(testdir):
|
||||
"""Check that the "mock_use_standalone" is being used.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test(mocker):
|
||||
m = mocker.Mock()
|
||||
m('fo')
|
||||
m.assert_called_once_with('', bar=4)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
if PY38:
|
||||
expected_lines = [
|
||||
"*AssertionError: expected call not found.",
|
||||
"*Expected: mock('', bar=4)",
|
||||
"*Actual: mock('fo')",
|
||||
]
|
||||
else:
|
||||
expected_lines = [
|
||||
"*AssertionError: Expected call: mock('', bar=4)*",
|
||||
"*Actual call: mock('fo')*",
|
||||
]
|
||||
expected_lines += [
|
||||
"*pytest introspection follows:*",
|
||||
"*Args:",
|
||||
"*assert ('fo',) == ('',)",
|
||||
"*At index 0 diff: 'fo' != ''*",
|
||||
"*Use -v to get the full diff*",
|
||||
"*Kwargs:*",
|
||||
"*assert {} == {'bar': 4}*",
|
||||
"*Right contains more items:*",
|
||||
"*{'bar': 4}*",
|
||||
"*Use -v to get the full diff*",
|
||||
]
|
||||
result.stdout.fnmatch_lines(expected_lines)
|
||||
|
||||
|
||||
def test_assert_called_with_unicode_arguments(mocker):
|
||||
"""Test bug in assert_call_with called with non-ascii unicode string (#91)"""
|
||||
stub = mocker.stub()
|
||||
stub(b"l\xc3\xb6k".decode("UTF-8"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
stub.assert_called_with(u"lak")
|
||||
|
||||
|
||||
def test_plain_stopall(testdir):
|
||||
"""patch.stopall() in a test should not cause an error during unconfigure (#137)"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import random
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 100)
|
||||
|
||||
def test_get_random_number(mocker):
|
||||
patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
|
||||
patcher.start()
|
||||
assert get_random_number() == 5
|
||||
mocker.mock_module.patch.stopall()
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
assert "RuntimeError" not in result.stderr.str()
|
1049
tests/test_pytest_mock.py
Normal file
1049
tests/test_pytest_mock.py
Normal file
|
@ -0,0 +1,1049 @@
|
|||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from pytest_mock import PytestMockWarning
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
# could not make some of the tests work on PyPy, patches are welcome!
|
||||
skip_pypy = pytest.mark.skipif(
|
||||
platform.python_implementation() == "PyPy", reason="could not make it work on pypy"
|
||||
)
|
||||
|
||||
# Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0
|
||||
NEW_FORMATTING = sys.version_info >= (3, 8)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def needs_assert_rewrite(pytestconfig):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
option = pytestconfig.getoption("assertmode")
|
||||
if option != "rewrite":
|
||||
pytest.skip(
|
||||
"this test needs assertion rewrite to work but current option "
|
||||
'is "{}"'.format(option)
|
||||
)
|
||||
|
||||
|
||||
class UnixFS:
|
||||
"""
|
||||
Wrapper to os functions to simulate a Unix file system, used for testing
|
||||
the mock fixture.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def rm(cls, filename):
|
||||
os.remove(filename)
|
||||
|
||||
@classmethod
|
||||
def ls(cls, path):
|
||||
return os.listdir(path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def check_unix_fs_mocked(
|
||||
tmpdir: Any, mocker: MockerFixture
|
||||
) -> Callable[[Any, Any], None]:
|
||||
"""
|
||||
performs a standard test in a UnixFS, assuming that both `os.remove` and
|
||||
`os.listdir` have been mocked previously.
|
||||
"""
|
||||
|
||||
def check(mocked_rm, mocked_ls):
|
||||
assert mocked_rm is os.remove
|
||||
assert mocked_ls is os.listdir
|
||||
|
||||
file_name = tmpdir / "foo.txt"
|
||||
file_name.ensure()
|
||||
|
||||
UnixFS.rm(str(file_name))
|
||||
mocked_rm.assert_called_once_with(str(file_name))
|
||||
assert os.path.isfile(str(file_name))
|
||||
|
||||
mocked_ls.return_value = ["bar.txt"]
|
||||
assert UnixFS.ls(str(tmpdir)) == ["bar.txt"]
|
||||
mocked_ls.assert_called_once_with(str(tmpdir))
|
||||
|
||||
mocker.stopall()
|
||||
|
||||
assert UnixFS.ls(str(tmpdir)) == ["foo.txt"]
|
||||
UnixFS.rm(str(file_name))
|
||||
assert not os.path.isfile(str(file_name))
|
||||
|
||||
return check
|
||||
|
||||
|
||||
def mock_using_patch_object(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]:
|
||||
return mocker.patch.object(os, "remove"), mocker.patch.object(os, "listdir")
|
||||
|
||||
|
||||
def mock_using_patch(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]:
|
||||
return mocker.patch("os.remove"), mocker.patch("os.listdir")
|
||||
|
||||
|
||||
def mock_using_patch_multiple(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]:
|
||||
r = mocker.patch.multiple("os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT)
|
||||
return r["remove"], r["listdir"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_fs", [mock_using_patch_object, mock_using_patch, mock_using_patch_multiple]
|
||||
)
|
||||
def test_mock_patches(
|
||||
mock_fs: Any,
|
||||
mocker: MockerFixture,
|
||||
check_unix_fs_mocked: Callable[[Any, Any], None],
|
||||
) -> None:
|
||||
"""
|
||||
Installs mocks into `os` functions and performs a standard testing of
|
||||
mock functionality. We parametrize different mock methods to ensure
|
||||
all (intended, at least) mock API is covered.
|
||||
"""
|
||||
# mock it twice on purpose to ensure we unmock it correctly later
|
||||
mock_fs(mocker)
|
||||
mocked_rm, mocked_ls = mock_fs(mocker)
|
||||
check_unix_fs_mocked(mocked_rm, mocked_ls)
|
||||
mocker.resetall()
|
||||
mocker.stopall()
|
||||
|
||||
|
||||
def test_mock_patch_dict(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
Testing
|
||||
:param mock:
|
||||
"""
|
||||
x = {"original": 1}
|
||||
mocker.patch.dict(x, values=[("new", 10)], clear=True)
|
||||
assert x == {"new": 10}
|
||||
mocker.stopall()
|
||||
assert x == {"original": 1}
|
||||
|
||||
|
||||
def test_mock_patch_dict_resetall(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
We can call resetall after patching a dict.
|
||||
:param mock:
|
||||
"""
|
||||
x = {"original": 1}
|
||||
mocker.patch.dict(x, values=[("new", 10)], clear=True)
|
||||
assert x == {"new": 10}
|
||||
mocker.resetall()
|
||||
assert x == {"new": 10}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"ANY",
|
||||
"call",
|
||||
"create_autospec",
|
||||
"MagicMock",
|
||||
"Mock",
|
||||
"mock_open",
|
||||
"NonCallableMock",
|
||||
"PropertyMock",
|
||||
"sentinel",
|
||||
pytest.param(
|
||||
"seal",
|
||||
marks=pytest.mark.skipif(
|
||||
sys.version_info < (3, 7), reason="seal is present on 3.7 and above"
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_mocker_aliases(name: str, pytestconfig: Any) -> None:
|
||||
from pytest_mock._util import get_mock_module
|
||||
|
||||
mock_module = get_mock_module(pytestconfig)
|
||||
|
||||
mocker = MockerFixture(pytestconfig)
|
||||
assert getattr(mocker, name) is getattr(mock_module, name)
|
||||
|
||||
|
||||
def test_mocker_resetall(mocker: MockerFixture) -> None:
|
||||
listdir = mocker.patch("os.listdir", return_value="foo")
|
||||
open = mocker.patch("os.open", side_effect=["bar", "baz"])
|
||||
|
||||
assert listdir("/tmp") == "foo"
|
||||
assert open("/tmp/foo.txt") == "bar"
|
||||
listdir.assert_called_once_with("/tmp")
|
||||
open.assert_called_once_with("/tmp/foo.txt")
|
||||
|
||||
mocker.resetall()
|
||||
|
||||
assert not listdir.called
|
||||
assert not open.called
|
||||
assert listdir.return_value == "foo"
|
||||
assert list(open.side_effect) == ["baz"]
|
||||
|
||||
mocker.resetall(return_value=True, side_effect=True)
|
||||
|
||||
assert isinstance(listdir.return_value, mocker.Mock)
|
||||
assert open.side_effect is None
|
||||
|
||||
|
||||
class TestMockerStub:
|
||||
def test_call(self, mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub("foo", "bar")
|
||||
stub.assert_called_once_with("foo", "bar")
|
||||
|
||||
def test_repr_with_no_name(self, mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
assert "name" not in repr(stub)
|
||||
|
||||
def test_repr_with_name(self, mocker: MockerFixture) -> None:
|
||||
test_name = "funny walk"
|
||||
stub = mocker.stub(name=test_name)
|
||||
assert "name={!r}".format(test_name) in repr(stub)
|
||||
|
||||
def __test_failure_message(self, mocker: MockerFixture, **kwargs: Any) -> None:
|
||||
expected_name = kwargs.get("name") or "mock"
|
||||
if NEW_FORMATTING:
|
||||
msg = "expected call not found.\nExpected: {0}()\nActual: not called."
|
||||
else:
|
||||
msg = "Expected call: {0}()\nNot called"
|
||||
expected_message = msg.format(expected_name)
|
||||
stub = mocker.stub(**kwargs)
|
||||
with pytest.raises(AssertionError) as exc_info:
|
||||
stub.assert_called_with()
|
||||
assert str(exc_info.value) == expected_message
|
||||
|
||||
def test_failure_message_with_no_name(self, mocker: MagicMock) -> None:
|
||||
self.__test_failure_message(mocker)
|
||||
|
||||
@pytest.mark.parametrize("name", (None, "", "f", "The Castle of aaarrrrggh"))
|
||||
def test_failure_message_with_name(self, mocker: MagicMock, name: str) -> None:
|
||||
self.__test_failure_message(mocker, name=name)
|
||||
|
||||
|
||||
def test_instance_method_spy(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
spy = mocker.spy(foo, "bar")
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
# Ref: https://docs.python.org/3/library/exceptions.html#exception-hierarchy
|
||||
@pytest.mark.parametrize(
|
||||
"exc_cls",
|
||||
(
|
||||
BaseException,
|
||||
Exception,
|
||||
GeneratorExit, # BaseException
|
||||
KeyboardInterrupt, # BaseException
|
||||
RuntimeError, # regular Exception
|
||||
SystemExit, # BaseException
|
||||
),
|
||||
)
|
||||
def test_instance_method_spy_exception(
|
||||
exc_cls: Type[BaseException],
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
class Foo:
|
||||
def bar(self, arg):
|
||||
raise exc_cls("Error with {}".format(arg))
|
||||
|
||||
foo = Foo()
|
||||
spy = mocker.spy(foo, "bar")
|
||||
|
||||
expected_calls = []
|
||||
for i, v in enumerate([10, 20]):
|
||||
with pytest.raises(exc_cls, match="Error with {}".format(v)):
|
||||
foo.bar(arg=v)
|
||||
|
||||
expected_calls.append(mocker.call(arg=v))
|
||||
assert foo.bar.call_args_list == expected_calls # type:ignore[attr-defined]
|
||||
assert str(spy.spy_exception) == "Error with {}".format(v)
|
||||
|
||||
|
||||
def test_instance_method_spy_autospec_true(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
foo = Foo()
|
||||
spy = mocker.spy(foo, "bar")
|
||||
with pytest.raises(
|
||||
AttributeError, match="'function' object has no attribute 'fake_assert_method'"
|
||||
):
|
||||
spy.fake_assert_method(arg=5)
|
||||
|
||||
|
||||
def test_spy_reset(mocker: MockerFixture) -> None:
|
||||
class Foo(object):
|
||||
def bar(self, x):
|
||||
if x == 0:
|
||||
raise ValueError("invalid x")
|
||||
return x * 3
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert spy.spy_return is None
|
||||
assert spy.spy_exception is None
|
||||
|
||||
Foo().bar(10)
|
||||
assert spy.spy_return == 30
|
||||
assert spy.spy_exception is None
|
||||
|
||||
# Testing spy can still be reset (#237).
|
||||
mocker.resetall()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
Foo().bar(0)
|
||||
assert spy.spy_return is None
|
||||
assert str(spy.spy_exception) == "invalid x"
|
||||
|
||||
Foo().bar(15)
|
||||
assert spy.spy_return == 45
|
||||
assert spy.spy_exception is None
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_instance_method_by_class_spy(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
||||
assert spy.call_args_list == calls
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_instance_method_by_subclass_spy(mocker: MockerFixture) -> None:
|
||||
class Base:
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
foo = Foo()
|
||||
other = Foo()
|
||||
assert foo.bar(arg=10) == 20
|
||||
assert other.bar(arg=10) == 20
|
||||
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
||||
assert spy.call_args_list == calls
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_spy(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
@classmethod
|
||||
def bar(cls, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_spy_autospec_false(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
@classmethod
|
||||
def bar(cls, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
spy.fake_assert_method()
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_subclass_spy(mocker: MockerFixture) -> None:
|
||||
class Base:
|
||||
@classmethod
|
||||
def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_class_method_with_metaclass_spy(mocker: MockerFixture) -> None:
|
||||
class MetaFoo(type):
|
||||
pass
|
||||
|
||||
class Foo:
|
||||
|
||||
__metaclass__ = MetaFoo
|
||||
|
||||
@classmethod
|
||||
def bar(cls, arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_static_method_spy(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def bar(arg):
|
||||
return arg * 2
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@skip_pypy
|
||||
def test_static_method_subclass_spy(mocker: MockerFixture) -> None:
|
||||
class Base:
|
||||
@staticmethod
|
||||
def bar(arg):
|
||||
return arg * 2
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
spy = mocker.spy(Foo, "bar")
|
||||
assert Foo.bar(arg=10) == 20
|
||||
Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
|
||||
assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
|
||||
spy.assert_called_once_with(arg=10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
def test_callable_like_spy(testdir: Any, mocker: MockerFixture) -> None:
|
||||
testdir.makepyfile(
|
||||
uut="""
|
||||
class CallLike(object):
|
||||
def __call__(self, x):
|
||||
return x * 2
|
||||
|
||||
call_like = CallLike()
|
||||
"""
|
||||
)
|
||||
testdir.syspathinsert()
|
||||
|
||||
uut = __import__("uut")
|
||||
|
||||
spy = mocker.spy(uut, "call_like")
|
||||
uut.call_like(10)
|
||||
spy.assert_called_once_with(10)
|
||||
assert spy.spy_return == 20
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_instance_async_method_spy(mocker: MockerFixture) -> None:
|
||||
class Foo:
|
||||
async def bar(self, arg):
|
||||
return arg * 2
|
||||
|
||||
foo = Foo()
|
||||
spy = mocker.spy(foo, "bar")
|
||||
|
||||
result = await foo.bar(10)
|
||||
|
||||
spy.assert_called_once_with(10)
|
||||
assert result == 20
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_traceback() -> Generator[None, None, None]:
|
||||
"""
|
||||
Assert that this file is at the top of the filtered traceback
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except AssertionError as e:
|
||||
assert e.__traceback__.tb_frame.f_code.co_filename == __file__ # type:ignore
|
||||
else:
|
||||
raise AssertionError("DID NOT RAISE")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_argument_introspection(left: Any, right: Any) -> Generator[None, None, None]:
|
||||
"""
|
||||
Assert detailed argument introspection is used
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except AssertionError as e:
|
||||
# this may be a bit too assuming, but seems nicer then hard-coding
|
||||
import _pytest.assertion.util as util
|
||||
|
||||
# NOTE: we assert with either verbose or not, depending on how our own
|
||||
# test was run by examining sys.argv
|
||||
verbose = any(a.startswith("-v") for a in sys.argv)
|
||||
expected = "\n ".join(util._compare_eq_iterable(left, right, verbose))
|
||||
assert expected in str(e)
|
||||
else:
|
||||
raise AssertionError("DID NOT RAISE")
|
||||
|
||||
|
||||
def test_assert_not_called_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub.assert_not_called()
|
||||
stub()
|
||||
with assert_traceback():
|
||||
stub.assert_not_called()
|
||||
|
||||
|
||||
def test_assert_called_with_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_called_with("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_with("bar")
|
||||
|
||||
|
||||
def test_assert_called_once_with_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_called_once_with("foo")
|
||||
stub("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_once_with("foo")
|
||||
|
||||
|
||||
def test_assert_called_once_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
if not hasattr(stub, "assert_called_once"):
|
||||
pytest.skip("assert_called_once not available")
|
||||
stub("foo")
|
||||
stub.assert_called_once()
|
||||
stub("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_called_once()
|
||||
|
||||
|
||||
def test_assert_called_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
if not hasattr(stub, "assert_called"):
|
||||
pytest.skip("assert_called_once not available")
|
||||
with assert_traceback():
|
||||
stub.assert_called()
|
||||
stub("foo")
|
||||
stub.assert_called()
|
||||
stub("foo")
|
||||
stub.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_assert_called_args_with_introspection(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
|
||||
complex_args = ("a", 1, {"test"})
|
||||
wrong_args = ("b", 2, {"jest"})
|
||||
|
||||
stub(*complex_args)
|
||||
stub.assert_called_with(*complex_args)
|
||||
stub.assert_called_once_with(*complex_args)
|
||||
|
||||
with assert_argument_introspection(complex_args, wrong_args):
|
||||
stub.assert_called_with(*wrong_args)
|
||||
stub.assert_called_once_with(*wrong_args)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_assert_called_kwargs_with_introspection(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
|
||||
complex_kwargs = dict(foo={"bar": 1, "baz": "spam"})
|
||||
wrong_kwargs = dict(foo={"goo": 1, "baz": "bran"})
|
||||
|
||||
stub(**complex_kwargs)
|
||||
stub.assert_called_with(**complex_kwargs)
|
||||
stub.assert_called_once_with(**complex_kwargs)
|
||||
|
||||
with assert_argument_introspection(complex_kwargs, wrong_kwargs):
|
||||
stub.assert_called_with(**wrong_kwargs)
|
||||
stub.assert_called_once_with(**wrong_kwargs)
|
||||
|
||||
|
||||
def test_assert_any_call_wrapper(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub("foo")
|
||||
stub.assert_any_call("foo")
|
||||
with assert_traceback():
|
||||
stub.assert_any_call("bar")
|
||||
|
||||
|
||||
def test_assert_has_calls(mocker: MockerFixture) -> None:
|
||||
stub = mocker.stub()
|
||||
stub("foo")
|
||||
stub.assert_has_calls([mocker.call("foo")])
|
||||
with assert_traceback():
|
||||
stub.assert_has_calls([mocker.call("bar")])
|
||||
|
||||
|
||||
def test_monkeypatch_ini(testdir: Any, mocker: MockerFixture) -> None:
|
||||
# Make sure the following function actually tests something
|
||||
stub = mocker.stub()
|
||||
assert stub.assert_called_with.__module__ != stub.__module__
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import py.code
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
assert stub.assert_called_with.__module__ == stub.__module__
|
||||
"""
|
||||
)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
mock_traceback_monkeypatch = false
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_parse_ini_boolean() -> None:
|
||||
from pytest_mock._util import parse_ini_boolean
|
||||
|
||||
assert parse_ini_boolean("True") is True
|
||||
assert parse_ini_boolean("false") is False
|
||||
with pytest.raises(ValueError):
|
||||
parse_ini_boolean("foo")
|
||||
|
||||
|
||||
def test_patched_method_parameter_name(mocker: MockerFixture) -> None:
|
||||
"""Test that our internal code uses uncommon names when wrapping other
|
||||
"mock" methods to avoid conflicts with user code (#31).
|
||||
"""
|
||||
|
||||
class Request:
|
||||
@classmethod
|
||||
def request(cls, method, args):
|
||||
pass
|
||||
|
||||
m = mocker.patch.object(Request, "request")
|
||||
Request.request(method="get", args={"type": "application/json"})
|
||||
m.assert_called_once_with(method="get", args={"type": "application/json"})
|
||||
|
||||
|
||||
def test_monkeypatch_native(testdir: Any) -> None:
|
||||
"""Automatically disable monkeypatching when --tb=native."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
stub(1, greet='hello')
|
||||
stub.assert_called_once_with(1, greet='hey')
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess("--tb=native")
|
||||
assert result.ret == 1
|
||||
assert "During handling of the above exception" not in result.stdout.str()
|
||||
assert "Differing items:" not in result.stdout.str()
|
||||
traceback_lines = [
|
||||
x
|
||||
for x in result.stdout.str().splitlines()
|
||||
if "Traceback (most recent call last)" in x
|
||||
]
|
||||
assert (
|
||||
len(traceback_lines) == 1
|
||||
) # make sure there are no duplicated tracebacks (#44)
|
||||
|
||||
|
||||
def test_monkeypatch_no_terminal(testdir: Any) -> None:
|
||||
"""Don't crash without 'terminal' plugin."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
stub = mocker.stub()
|
||||
stub(1, greet='hello')
|
||||
stub.assert_called_once_with(1, greet='hey')
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess("-p", "no:terminal", "-s")
|
||||
assert result.ret == 1
|
||||
assert result.stdout.lines == []
|
||||
|
||||
|
||||
def test_standalone_mock(testdir: Any) -> None:
|
||||
"""Check that the "mock_use_standalone" is being used."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
mock_use_standalone_module = true
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert result.ret == 3
|
||||
result.stderr.fnmatch_lines(["*No module named 'mock'*"])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_detailed_introspection(testdir: Any) -> None:
|
||||
"""Check that the "mock_use_standalone" is being used."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test(mocker):
|
||||
m = mocker.Mock()
|
||||
m('fo')
|
||||
m.assert_called_once_with('', bar=4)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
if NEW_FORMATTING:
|
||||
expected_lines = [
|
||||
"*AssertionError: expected call not found.",
|
||||
"*Expected: mock('', bar=4)",
|
||||
"*Actual: mock('fo')",
|
||||
]
|
||||
else:
|
||||
expected_lines = [
|
||||
"*AssertionError: Expected call: mock('', bar=4)*",
|
||||
"*Actual call: mock('fo')*",
|
||||
]
|
||||
expected_lines += [
|
||||
"*pytest introspection follows:*",
|
||||
"*Args:",
|
||||
"*assert ('fo',) == ('',)",
|
||||
"*At index 0 diff: 'fo' != ''*",
|
||||
"*Use -v to get the full diff*",
|
||||
"*Kwargs:*",
|
||||
"*assert {} == {'bar': 4}*",
|
||||
"*Right contains* more item*",
|
||||
"*{'bar': 4}*",
|
||||
"*Use -v to get the full diff*",
|
||||
]
|
||||
result.stdout.fnmatch_lines(expected_lines)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 8), reason="AsyncMock is present on 3.8 and above"
|
||||
)
|
||||
@pytest.mark.usefixtures("needs_assert_rewrite")
|
||||
def test_detailed_introspection_async(testdir: Any) -> None:
|
||||
"""Check that the "mock_use_standalone" is being used."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test(mocker):
|
||||
m = mocker.AsyncMock()
|
||||
await m('fo')
|
||||
m.assert_awaited_once_with('', bar=4)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
expected_lines = [
|
||||
"*AssertionError: expected await not found.",
|
||||
"*Expected: mock('', bar=4)",
|
||||
"*Actual: mock('fo')",
|
||||
"*pytest introspection follows:*",
|
||||
"*Args:",
|
||||
"*assert ('fo',) == ('',)",
|
||||
"*At index 0 diff: 'fo' != ''*",
|
||||
"*Use -v to get the full diff*",
|
||||
"*Kwargs:*",
|
||||
"*assert {} == {'bar': 4}*",
|
||||
"*Right contains* more item*",
|
||||
"*{'bar': 4}*",
|
||||
"*Use -v to get the full diff*",
|
||||
]
|
||||
result.stdout.fnmatch_lines(expected_lines)
|
||||
|
||||
|
||||
def test_missing_introspection(testdir: Any) -> None:
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(mocker):
|
||||
mock = mocker.Mock()
|
||||
mock('foo')
|
||||
mock('test')
|
||||
mock.assert_called_once_with('test')
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
assert "pytest introspection follows:" not in result.stdout.str()
|
||||
|
||||
|
||||
def test_assert_called_with_unicode_arguments(mocker: MockerFixture) -> None:
|
||||
"""Test bug in assert_call_with called with non-ascii unicode string (#91)"""
|
||||
stub = mocker.stub()
|
||||
stub(b"l\xc3\xb6k".decode("UTF-8"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
stub.assert_called_with("lak")
|
||||
|
||||
|
||||
def test_plain_stopall(testdir: Any) -> None:
|
||||
"""patch.stopall() in a test should not cause an error during unconfigure (#137)"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import random
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 100)
|
||||
|
||||
def test_get_random_number(mocker):
|
||||
patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
|
||||
patcher.start()
|
||||
assert get_random_number() == 5
|
||||
mocker.mock_module.patch.stopall()
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
assert "RuntimeError" not in result.stderr.str()
|
||||
|
||||
|
||||
def test_warn_patch_object_context_manager(mocker: MockerFixture) -> None:
|
||||
class A:
|
||||
def doIt(self):
|
||||
return False
|
||||
|
||||
a = A()
|
||||
|
||||
expected_warning_msg = (
|
||||
"Mocks returned by pytest-mock do not need to be used as context managers. "
|
||||
"The mocker fixture automatically undoes mocking at the end of a test. "
|
||||
"This warning can be ignored if it was triggered by mocking a context manager. "
|
||||
"https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
|
||||
)
|
||||
|
||||
with pytest.warns(
|
||||
PytestMockWarning, match=re.escape(expected_warning_msg)
|
||||
) as warn_record:
|
||||
with mocker.patch.object(a, "doIt", return_value=True):
|
||||
assert a.doIt() is True
|
||||
|
||||
assert warn_record[0].filename == __file__
|
||||
|
||||
|
||||
def test_warn_patch_context_manager(mocker: MockerFixture) -> None:
|
||||
expected_warning_msg = (
|
||||
"Mocks returned by pytest-mock do not need to be used as context managers. "
|
||||
"The mocker fixture automatically undoes mocking at the end of a test. "
|
||||
"This warning can be ignored if it was triggered by mocking a context manager. "
|
||||
"https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
|
||||
)
|
||||
|
||||
with pytest.warns(
|
||||
PytestMockWarning, match=re.escape(expected_warning_msg)
|
||||
) as warn_record:
|
||||
with mocker.patch("json.loads"):
|
||||
pass
|
||||
|
||||
assert warn_record[0].filename == __file__
|
||||
|
||||
|
||||
def test_context_manager_patch_example(mocker: MockerFixture) -> None:
|
||||
"""Our message about misusing mocker as a context manager should not affect mocking
|
||||
context managers (see #192)"""
|
||||
|
||||
class dummy_module:
|
||||
class MyContext:
|
||||
def __enter__(self, *args, **kwargs):
|
||||
return 10
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def my_func():
|
||||
with dummy_module.MyContext() as v:
|
||||
return v
|
||||
|
||||
m = mocker.patch.object(dummy_module, "MyContext")
|
||||
assert isinstance(my_func(), mocker.MagicMock)
|
||||
|
||||
|
||||
def test_patch_context_manager_with_context_manager(mocker: MockerFixture) -> None:
|
||||
"""Test that no warnings are issued when an object patched with
|
||||
patch.context_manager is used as a context manager (#221)"""
|
||||
|
||||
class A:
|
||||
def doIt(self):
|
||||
return False
|
||||
|
||||
a = A()
|
||||
|
||||
with pytest.warns(None) as warn_record:
|
||||
with mocker.patch.context_manager(a, "doIt", return_value=True):
|
||||
assert a.doIt() is True
|
||||
|
||||
assert len(warn_record) == 0
|
||||
|
||||
|
||||
def test_abort_patch_context_manager_with_stale_pyc(testdir: Any) -> None:
|
||||
"""Ensure we don't trigger an error in case the frame where mocker.patch is being
|
||||
used doesn't have a 'context' (#169)"""
|
||||
import compileall
|
||||
|
||||
py_fn = testdir.makepyfile(
|
||||
c="""
|
||||
class C:
|
||||
x = 1
|
||||
|
||||
def check(mocker):
|
||||
mocker.patch.object(C, "x", 2)
|
||||
assert C.x == 2
|
||||
"""
|
||||
)
|
||||
testdir.syspathinsert()
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
from c import check
|
||||
def test_foo(mocker):
|
||||
check(mocker)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(passed=1)
|
||||
|
||||
assert compileall.compile_file(str(py_fn), legacy=True)
|
||||
|
||||
pyc_fn = str(py_fn) + "c"
|
||||
assert os.path.isfile(pyc_fn)
|
||||
|
||||
py_fn.remove()
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(passed=1)
|
||||
|
||||
|
||||
def test_used_with_class_scope(testdir: Any) -> None:
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import random
|
||||
import unittest
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 1)
|
||||
|
||||
@pytest.fixture(autouse=True, scope="class")
|
||||
def randint_mock(class_mocker):
|
||||
return class_mocker.patch("random.randint", lambda x, y: 5)
|
||||
|
||||
class TestGetRandomNumber(unittest.TestCase):
|
||||
def test_get_random_number(self):
|
||||
assert get_random_number() == 5
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert "AssertionError" not in result.stderr.str()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
|
||||
|
||||
def test_used_with_module_scope(testdir: Any) -> None:
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import random
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 1)
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def randint_mock(module_mocker):
|
||||
return module_mocker.patch("random.randint", lambda x, y: 5)
|
||||
|
||||
def test_get_random_number():
|
||||
assert get_random_number() == 5
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert "AssertionError" not in result.stderr.str()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
|
||||
|
||||
def test_used_with_package_scope(testdir: Any) -> None:
|
||||
"""..."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import random
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 1)
|
||||
|
||||
@pytest.fixture(autouse=True, scope="package")
|
||||
def randint_mock(package_mocker):
|
||||
return package_mocker.patch("random.randint", lambda x, y: 5)
|
||||
|
||||
def test_get_random_number():
|
||||
assert get_random_number() == 5
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert "AssertionError" not in result.stderr.str()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
|
||||
|
||||
def test_used_with_session_scope(testdir: Any) -> None:
|
||||
"""..."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import random
|
||||
|
||||
def get_random_number():
|
||||
return random.randint(0, 1)
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def randint_mock(session_mocker):
|
||||
return session_mocker.patch("random.randint", lambda x, y: 5)
|
||||
|
||||
def test_get_random_number():
|
||||
assert get_random_number() == 5
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert "AssertionError" not in result.stderr.str()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
17
tox.ini
17
tox.ini
|
@ -1,26 +1,33 @@
|
|||
[tox]
|
||||
envlist = py{27,34,35,36,37}, linting, norewrite
|
||||
minversion = 3.5.3
|
||||
envlist = py{35,36,37,38,39}, linting, norewrite
|
||||
|
||||
[testenv]
|
||||
passenv = USER USERNAME
|
||||
deps =
|
||||
coverage
|
||||
pytest-asyncio
|
||||
commands =
|
||||
coverage run --append --source=pytest_mock.py -m pytest test_pytest_mock.py
|
||||
coverage run --append --source={envsitepackagesdir}/pytest_mock -m pytest tests
|
||||
|
||||
[testenv:norewrite]
|
||||
commands =
|
||||
pytest test_pytest_mock.py --assert=plain -ra
|
||||
pytest tests --assert=plain
|
||||
|
||||
[testenv:linting]
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
extras = dev
|
||||
basepython = python3.6
|
||||
commands = pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:mypy]
|
||||
skip_install = true
|
||||
deps =
|
||||
mypy==0.800
|
||||
commands = mypy {posargs:src tests}
|
||||
|
||||
[pytest]
|
||||
addopts = -ra
|
||||
addopts = -r a
|
||||
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
|
|
Loading…
Reference in a new issue