New upstream version 3.6.1

This commit is contained in:
Diego M. Rodriguez 2021-08-31 11:33:03 +02:00
parent e5f34a66ea
commit d47623bc9f
30 changed files with 2482 additions and 1349 deletions

4
.github/FUNDING.yml vendored Normal file
View 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
View 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
View file

@ -56,7 +56,7 @@ docs/_build/
# Virtual Envs
.env*
_pytest_mock_version.py
# IDE
.idea
.vscode
/src/pytest_mock/_version.py

View file

@ -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]

View file

@ -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

View file

@ -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
View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -1,8 +0,0 @@
pytest>=2.7
[:python_version < "3.0"]
mock
[dev]
pre-commit
tox

View file

@ -1,2 +0,0 @@
_pytest_mock_version
pytest_mock

View file

@ -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)

View file

@ -1,6 +1,3 @@
[bdist_wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0

View file

@ -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",
],
)

View file

@ -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

View 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

View file

@ -0,0 +1,6 @@
pytest>=5.0
[dev]
pre-commit
tox
pytest-asyncio

View file

@ -0,0 +1 @@
pytest_mock

View 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
View 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)

View file

@ -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
View 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
View file

View 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
View 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
View file

@ -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