From b4297c26162425ecafb4b86d85facd8456d3c697 Mon Sep 17 00:00:00 2001 From: Julian Gilbey Date: Fri, 15 Jul 2022 07:47:42 +0100 Subject: [PATCH] New upstream version 3.8.2 --- .github/workflows/deploy.yml | 39 +++ .github/workflows/main.yml | 72 ----- .github/workflows/test.yml | 38 +++ .pre-commit-config.yaml | 8 +- CHANGELOG.rst | 28 +- PKG-INFO | 357 +--------------------- README.rst | 353 +-------------------- docs/about.rst | 20 ++ docs/changelog.rst | 7 + docs/conf.py | 50 +++ docs/configuration.rst | 75 +++++ docs/contributing.rst | 21 ++ docs/index.rst | 49 +++ docs/remarks.rst | 122 ++++++++ docs/requirements.txt | 3 + docs/usage.rst | 116 +++++++ src/pytest_mock.egg-info/PKG-INFO | 357 +--------------------- src/pytest_mock.egg-info/SOURCES.txt | 12 +- src/pytest_mock.egg-info/entry_points.txt | 1 - src/pytest_mock/_version.py | 4 +- src/pytest_mock/plugin.py | 18 ++ tests/test_pytest_mock.py | 32 +- tox.ini | 8 + 23 files changed, 658 insertions(+), 1132 deletions(-) create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/test.yml create mode 100644 docs/about.rst create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/configuration.rst create mode 100644 docs/contributing.rst create mode 100644 docs/index.rst create mode 100644 docs/remarks.rst create mode 100644 docs/requirements.txt create mode 100644 docs/usage.rst diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3f542a8 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: deploy + +on: + push: + tags: + - "v*" + +jobs: + + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: "3.7" + - name: Install wheel + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: | + python -m build + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_token }} + - name: Generate release notes + run: | + pip install pypandoc + sudo apt-get install pandoc + python scripts/gen-release-notes.py + - name: GitHub Release + uses: softprops/action-gh-release@v1 + with: + body_path: scripts/latest-release-notes.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 6969d9d..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: build - -on: [push, pull_request] - -jobs: - build: - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - python: ["3.7", "3.8", "3.9", "3.10"] - os: [ubuntu-latest, windows-latest] - include: - - python: "3.7" - tox_env: "py37" - - python: "3.8" - tox_env: "py38" - - python: "3.9" - tox_env: "py39" - - python: "3.10" - tox_env: "py310" - - steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - name: Install tox - run: | - python -m pip install --upgrade pip - pip install tox - - name: Test - run: | - tox -e ${{ matrix.tox_env }} - - deploy: - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - - runs-on: ubuntu-latest - - needs: build - - steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: "3.7" - - name: Install wheel - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: | - python -m build - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} - - name: Generate release notes - run: | - pip install pypandoc - python scripts/gen-release-notes.py - - name: GitHub Release - uses: softprops/action-gh-release@v1 - with: - body_path: scripts/latest-release-notes.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d4e0cb9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: test + +on: [push, pull_request] + +jobs: + + test: + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + python: ["3.7", "3.8", "3.9", "3.10"] + os: [ubuntu-latest, windows-latest] + include: + - python: "3.7" + tox_env: "py37" + - python: "3.8" + tox_env: "py38" + - python: "3.9" + tox_env: "py39" + - python: "3.10" + tox_env: "py310" + + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: | + tox -e ${{ matrix.tox_env }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c33218..0a5f8e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ exclude: '^($|.*\.bin)' repos: - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.6.0 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -19,12 +19,12 @@ repos: language: python additional_dependencies: [pygments, restructuredtext_lint] - repo: https://github.com/asottile/reorder_python_imports - rev: v2.6.0 + rev: v3.3.0 hooks: - id: reorder-python-imports args: ['--application-directories=.:src'] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 # NOTE: keep this in sync with tox.ini + rev: v0.961 # NOTE: keep this in sync with tox.ini hooks: - id: mypy files: ^(src|tests) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 37152e6..177ee3d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,28 @@ +Releases +======== + +3.8.2 (2022-07-05) +------------------ + +- Fixed `AsyncMock` support for Python 3.7+ in `mocker.async_stub` (`#302`_). + +.. _#302: https://github.com/pytest-dev/pytest-mock/pull/302 + +3.8.1 (2022-06-24) +------------------ + +* Fixed regression caused by an explicit ``mock`` dependency in the code (`#298`_). + +.. _#298: https://github.com/pytest-dev/pytest-mock/issues/298 + +3.8.0 (2022-06-24) +------------------ + +* Add ``MockerFixture.async_mock`` method. Thanks `@PerchunPak`_ for the PR (`#296`_). + +.. _@PerchunPak: https://github.com/PerchunPak +.. _#296: https://github.com/pytest-dev/pytest-mock/pull/296 + 3.7.0 (2022-01-28) ------------------ @@ -414,8 +439,7 @@ Breaking Changes option. Thanks `@asfaltboy`_ for the PR (`#36`_). -* ``mocker.stub()`` now allows passing in the name for the constructed `Mock - `_ +* ``mocker.stub()`` now allows passing in the name for the constructed ``Mock`` object instead of having to set it using the internal ``_mock_name`` attribute directly. This is useful for debugging as the name is used in the mock's ``repr`` string as well as related assertion failure messages. diff --git a/PKG-INFO b/PKG-INFO index db19171..94bd396 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pytest-mock -Version: 3.7.0 +Version: 3.8.2 Summary: Thin-wrapper around the mock package for easier use with pytest Home-page: https://github.com/pytest-dev/pytest-mock/ Author: Bruno Oliveira @@ -51,7 +51,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when comparing calls. -|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| +|python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit| .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg :target: https://pypi.python.org/pypi/pytest-mock @@ -59,7 +59,7 @@ comparing calls. .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg :target: https://anaconda.org/conda-forge/pytest-mock -.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg +.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg :target: https://github.com/pytest-dev/pytest-mock/actions .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master @@ -74,361 +74,22 @@ comparing calls. .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master -`Professionally supported pytest-mock is now available `_ +.. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest + :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest -Usage -===== -The ``mocker`` fixture has the same API as -`mock.patch `_, -supporting the same arguments: +`Professionally supported pytest-mock is available `_. -.. code-block:: python - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') +Documentation +============= -The supported methods are: - -* `mocker.patch `_ -* `mocker.patch.object `_ -* `mocker.patch.multiple `_ -* `mocker.patch.dict `_ -* `mocker.stopall `_ -* ``mocker.resetall()``: calls `reset_mock() `_ in all mocked objects up to this point. - -Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``: - -* `Mock `_ -* `MagicMock `_ -* `PropertyMock `_ -* `ANY `_ -* `DEFAULT `_ *(Version 1.4)* -* `call `_ *(Version 1.1)* -* `sentinel `_ *(Version 1.2)* -* `mock_open `_ -* `seal `_ *(Version 3.4)* - -It is also possible to use mocking functionality from fixtures of other scopes using -the appropriate mock fixture: - -* ``class_mocker`` -* ``module_mocker`` -* ``package_mocker`` -* ``session_mocker`` - -Type Annotations ----------------- - -*New in version 3.3.0.* - -``pytest-mock`` is fully type annotated, letting users use static type checkers to -test their code. - -The ``mocker`` fixture returns ``pytest_mock.MockerFixture`` which can be used -to annotate test functions: - -.. code-block:: python - - from pytest_mock import MockerFixture - - def test_foo(mocker: MockerFixture) -> None: - ... - -The type annotations have been checked with ``mypy``, which is the only -type checker supported at the moment; other type-checkers might work -but are not currently tested. - -Spy ---- - -The ``mocker.spy`` object acts exactly like the original method in all cases, except the spy -also tracks function/method calls, return values and exceptions raised. - -.. code-block:: python - - def test_spy_method(mocker): - class Foo(object): - def bar(self, v): - return v * 2 - - foo = Foo() - spy = mocker.spy(foo, 'bar') - assert foo.bar(21) == 42 - - spy.assert_called_once_with(21) - assert spy.spy_return == 42 - - def test_spy_function(mocker): - # mymodule declares `myfunction` which just returns 42 - import mymodule - - spy = mocker.spy(mymodule, "myfunction") - assert mymodule.myfunction() == 42 - assert spy.call_count == 1 - assert spy.spy_return == 42 - -The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions -are available (like ``assert_called_once_with`` or ``call_count`` in the examples above). - -In addition, spy objects contain two extra attributes: - -* ``spy_return``: contains the returned value of the spied function. -* ``spy_exception``: contain the last exception value raised by the spied function/method when - it was last called, or ``None`` if no exception was raised. - -Besides functions and normal methods, ``mocker.spy`` also works for class and static methods. - -As of version 3.0.0, ``mocker.spy`` also works with ``async def`` functions. - -.. note:: - - In versions earlier than ``2.0``, the attributes were called ``return_value`` and - ``side_effect`` respectively, but due to incompatibilities with ``unittest.mock`` - they had to be renamed (see `#175`_ for details). - - .. _#175: https://github.com/pytest-dev/pytest-mock/issues/175 - -Stub ----- - -The stub is a mock object that accepts any arguments and is useful to test callbacks. -It may receive an optional name that is shown in its ``repr``, useful for debugging. - -.. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - -Improved reporting of mock call assertion errors ------------------------------------------------- - -This plugin monkeypatches the mock library to improve pytest output for failures -of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback -entries from the ``mock`` module. - -It also adds introspection information on differing call arguments when -calling the helper methods. This features catches `AssertionError` raised in -the method, and uses pytest's own `advanced assertions`_ to return a better -diff:: - - - mocker = - - def test(mocker): - m = mocker.Mock() - m('fo') - > m.assert_called_once_with('', bar=4) - E AssertionError: Expected call: mock('', bar=4) - E Actual call: mock('fo') - E - E pytest introspection follows: - E - E Args: - E assert ('fo',) == ('',) - E At index 0 diff: 'fo' != '' - E Use -v to get the full diff - E Kwargs: - E assert {} == {'bar': 4} - E Right contains more items: - E {'bar': 4} - E Use -v to get the full diff - - - test_foo.py:6: AssertionError - ========================== 1 failed in 0.03 seconds =========================== - - -This is useful when asserting mock calls with many/nested arguments and trying -to quickly see the difference. - -This feature is probably safe, but if you encounter any problems it can be disabled in -your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - -Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying -mechanism used to suppress traceback entries from ``mock`` module does not work with that option -anyway plus it generates confusing messages on Python 3.5 due to exception chaining - -.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html - - -Use standalone "mock" package ------------------------------ - -*New in version 1.4.0.* - -Python 3 users might want to use a newest version of the ``mock`` package as published on PyPI -than the one that comes with the Python distribution. - -.. code-block:: ini - - [pytest] - mock_use_standalone_module = true - -This will force the plugin to import ``mock`` instead of the ``unittest.mock`` module bundled with -Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option -to use the ``mock`` package from PyPI anyway. - -Note about usage as context manager ------------------------------------ - -Although mocker's API is intentionally the same as ``mock.patch``'s, its use -as context manager and function decorator is **not** supported through the -fixture: - -.. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS - assert a.doIt() == True - -The purpose of this plugin is to make the use of context managers and -function decorators for mocking unnecessary, so it will emit a warning when used as such. - -If you really intend to mock a context manager, ``mocker.patch.context_manager`` exists -which won't issue the above warning. - - -Install -======= - -Install using `pip `_: - -.. code-block:: console - - $ pip install pytest-mock - -Changelog -========= - -Please consult the `changelog page`_. - -.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - -Why bother with a plugin? -========================= - -There are a number of different ``patch`` usages in the standard ``mock`` API, -but IMHO they don't scale very well when you have more than one or two -patches to apply. - -It may lead to an excessive nesting of ``with`` statements, breaking the flow -of the test: - -.. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - -One can use ``patch`` as a decorator to improve the flow of the test: - -.. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - -But this poses a few disadvantages: - -- test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; -- receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; -- you can't easily undo the mocking during the test execution; - -An alternative is to use ``contextlib.ExitStack`` to stack the context managers in a single level of indentation -to improve the flow of the test: - -.. code-block:: python - - import contextlib - import mock - - def test_unix_fs(): - with contextlib.ExitStack() as stack: - stack.enter_context(mock.patch('os.remove')) - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - stack.enter_context(mock.patch('os.listdir')) - assert UnixFS.ls('dir') == expected - # ... - - stack.enter_context(mock.patch('shutil.copy')) - UnixFS.cp('src', 'dst') - # ... - -But this is arguably a little more complex than using ``pytest-mock``. - -Contributing -============ - -Contributions are welcome! After cloning the repository, create a virtual env -and install ``pytest-mock`` in editable mode with ``dev`` extras: - -.. code-block:: console - - $ pip install --editable .[dev] - $ pre-commit install - -Tests are run with ``tox``, you can run the baseline environments before submitting a PR: - -.. code-block:: console - - $ tox -e py38,linting - -Style checks and formatting are done automatically during commit courtesy of -`pre-commit `_. +For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. -Security contact information -============================ - -To report a security vulnerability, please use the `Tidelift security contact `__. Tidelift will coordinate the fix and disclosure. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE - - diff --git a/README.rst b/README.rst index de5971c..125df31 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when comparing calls. -|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| +|python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit| .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg :target: https://pypi.python.org/pypi/pytest-mock @@ -33,7 +33,7 @@ comparing calls. .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg :target: https://anaconda.org/conda-forge/pytest-mock -.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg +.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg :target: https://github.com/pytest-dev/pytest-mock/actions .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master @@ -48,359 +48,22 @@ comparing calls. .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master -`Professionally supported pytest-mock is now available `_ +.. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest + :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest -Usage -===== -The ``mocker`` fixture has the same API as -`mock.patch `_, -supporting the same arguments: +`Professionally supported pytest-mock is available `_. -.. code-block:: python - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') +Documentation +============= -The supported methods are: - -* `mocker.patch `_ -* `mocker.patch.object `_ -* `mocker.patch.multiple `_ -* `mocker.patch.dict `_ -* `mocker.stopall `_ -* ``mocker.resetall()``: calls `reset_mock() `_ in all mocked objects up to this point. - -Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``: - -* `Mock `_ -* `MagicMock `_ -* `PropertyMock `_ -* `ANY `_ -* `DEFAULT `_ *(Version 1.4)* -* `call `_ *(Version 1.1)* -* `sentinel `_ *(Version 1.2)* -* `mock_open `_ -* `seal `_ *(Version 3.4)* - -It is also possible to use mocking functionality from fixtures of other scopes using -the appropriate mock fixture: - -* ``class_mocker`` -* ``module_mocker`` -* ``package_mocker`` -* ``session_mocker`` - -Type Annotations ----------------- - -*New in version 3.3.0.* - -``pytest-mock`` is fully type annotated, letting users use static type checkers to -test their code. - -The ``mocker`` fixture returns ``pytest_mock.MockerFixture`` which can be used -to annotate test functions: - -.. code-block:: python - - from pytest_mock import MockerFixture - - def test_foo(mocker: MockerFixture) -> None: - ... - -The type annotations have been checked with ``mypy``, which is the only -type checker supported at the moment; other type-checkers might work -but are not currently tested. - -Spy ---- - -The ``mocker.spy`` object acts exactly like the original method in all cases, except the spy -also tracks function/method calls, return values and exceptions raised. - -.. code-block:: python - - def test_spy_method(mocker): - class Foo(object): - def bar(self, v): - return v * 2 - - foo = Foo() - spy = mocker.spy(foo, 'bar') - assert foo.bar(21) == 42 - - spy.assert_called_once_with(21) - assert spy.spy_return == 42 - - def test_spy_function(mocker): - # mymodule declares `myfunction` which just returns 42 - import mymodule - - spy = mocker.spy(mymodule, "myfunction") - assert mymodule.myfunction() == 42 - assert spy.call_count == 1 - assert spy.spy_return == 42 - -The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions -are available (like ``assert_called_once_with`` or ``call_count`` in the examples above). - -In addition, spy objects contain two extra attributes: - -* ``spy_return``: contains the returned value of the spied function. -* ``spy_exception``: contain the last exception value raised by the spied function/method when - it was last called, or ``None`` if no exception was raised. - -Besides functions and normal methods, ``mocker.spy`` also works for class and static methods. - -As of version 3.0.0, ``mocker.spy`` also works with ``async def`` functions. - -.. note:: - - In versions earlier than ``2.0``, the attributes were called ``return_value`` and - ``side_effect`` respectively, but due to incompatibilities with ``unittest.mock`` - they had to be renamed (see `#175`_ for details). - - .. _#175: https://github.com/pytest-dev/pytest-mock/issues/175 - -Stub ----- - -The stub is a mock object that accepts any arguments and is useful to test callbacks. -It may receive an optional name that is shown in its ``repr``, useful for debugging. - -.. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - -Improved reporting of mock call assertion errors ------------------------------------------------- - -This plugin monkeypatches the mock library to improve pytest output for failures -of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback -entries from the ``mock`` module. - -It also adds introspection information on differing call arguments when -calling the helper methods. This features catches `AssertionError` raised in -the method, and uses pytest's own `advanced assertions`_ to return a better -diff:: - - - mocker = - - def test(mocker): - m = mocker.Mock() - m('fo') - > m.assert_called_once_with('', bar=4) - E AssertionError: Expected call: mock('', bar=4) - E Actual call: mock('fo') - E - E pytest introspection follows: - E - E Args: - E assert ('fo',) == ('',) - E At index 0 diff: 'fo' != '' - E Use -v to get the full diff - E Kwargs: - E assert {} == {'bar': 4} - E Right contains more items: - E {'bar': 4} - E Use -v to get the full diff - - - test_foo.py:6: AssertionError - ========================== 1 failed in 0.03 seconds =========================== - - -This is useful when asserting mock calls with many/nested arguments and trying -to quickly see the difference. - -This feature is probably safe, but if you encounter any problems it can be disabled in -your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - -Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying -mechanism used to suppress traceback entries from ``mock`` module does not work with that option -anyway plus it generates confusing messages on Python 3.5 due to exception chaining - -.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html - - -Use standalone "mock" package ------------------------------ - -*New in version 1.4.0.* - -Python 3 users might want to use a newest version of the ``mock`` package as published on PyPI -than the one that comes with the Python distribution. - -.. code-block:: ini - - [pytest] - mock_use_standalone_module = true - -This will force the plugin to import ``mock`` instead of the ``unittest.mock`` module bundled with -Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option -to use the ``mock`` package from PyPI anyway. - -Note about usage as context manager ------------------------------------ - -Although mocker's API is intentionally the same as ``mock.patch``'s, its use -as context manager and function decorator is **not** supported through the -fixture: - -.. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS - assert a.doIt() == True - -The purpose of this plugin is to make the use of context managers and -function decorators for mocking unnecessary, so it will emit a warning when used as such. - -If you really intend to mock a context manager, ``mocker.patch.context_manager`` exists -which won't issue the above warning. - - -Install -======= - -Install using `pip `_: - -.. code-block:: console - - $ pip install pytest-mock - -Changelog -========= - -Please consult the `changelog page`_. - -.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - -Why bother with a plugin? -========================= - -There are a number of different ``patch`` usages in the standard ``mock`` API, -but IMHO they don't scale very well when you have more than one or two -patches to apply. - -It may lead to an excessive nesting of ``with`` statements, breaking the flow -of the test: - -.. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - -One can use ``patch`` as a decorator to improve the flow of the test: - -.. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - -But this poses a few disadvantages: - -- test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; -- receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; -- you can't easily undo the mocking during the test execution; - -An alternative is to use ``contextlib.ExitStack`` to stack the context managers in a single level of indentation -to improve the flow of the test: - -.. code-block:: python - - import contextlib - import mock - - def test_unix_fs(): - with contextlib.ExitStack() as stack: - stack.enter_context(mock.patch('os.remove')) - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - stack.enter_context(mock.patch('os.listdir')) - assert UnixFS.ls('dir') == expected - # ... - - stack.enter_context(mock.patch('shutil.copy')) - UnixFS.cp('src', 'dst') - # ... - -But this is arguably a little more complex than using ``pytest-mock``. - -Contributing -============ - -Contributions are welcome! After cloning the repository, create a virtual env -and install ``pytest-mock`` in editable mode with ``dev`` extras: - -.. code-block:: console - - $ pip install --editable .[dev] - $ pre-commit install - -Tests are run with ``tox``, you can run the baseline environments before submitting a PR: - -.. code-block:: console - - $ tox -e py38,linting - -Style checks and formatting are done automatically during commit courtesy of -`pre-commit `_. +For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. -Security contact information -============================ - -To report a security vulnerability, please use the `Tidelift security contact `__. Tidelift will coordinate the fix and disclosure. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE diff --git a/docs/about.rst b/docs/about.rst new file mode 100644 index 0000000..f8d07db --- /dev/null +++ b/docs/about.rst @@ -0,0 +1,20 @@ +===== +About +===== + +Tidelift +======== + +If you use this plugin in a corporate environment, consider supporting ``pytest-mock`` via `Tidelift `_. + +License +======= + +Distributed under the terms of the `MIT`_ license. + +Security contact information +============================ + +To report a security vulnerability, please use the `Tidelift security contact `__. Tidelift will coordinate the fix and disclosure. + +.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..469c250 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,7 @@ +========= +Changelog +========= + +:hide-toc: + +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..61a1dc4 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,50 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html +# -- Path setup -------------------------------------------------------------- +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +# -- Project information ----------------------------------------------------- + +project = "pytest-mock" +copyright = "2022, Bruno Oliveira" +author = "Bruno Oliveira" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx_copybutton", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "furo" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 0000000..d1701d5 --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,75 @@ +============= +Configuration +============= + +Use standalone "mock" package +----------------------------- + +Python 3 users might want to use a newest version of the ``mock`` package as published on PyPI +than the one that comes with the Python distribution. + +.. code-block:: ini + + [pytest] + mock_use_standalone_module = true + +This will force the plugin to import ``mock`` instead of the ``unittest.mock`` module bundled with +Python 3.4+. + + + +Improved reporting of mock call assertion errors +------------------------------------------------ + +This plugin monkeypatches the mock library to improve pytest output for failures +of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback +entries from the ``mock`` module. + +It also adds introspection information on differing call arguments when +calling the helper methods. This features catches `AssertionError` raised in +the method, and uses pytest's own `advanced assertions`_ to return a better +diff:: + + + mocker = + + def test(mocker): + m = mocker.Mock() + m('fo') + > m.assert_called_once_with('', bar=4) + E AssertionError: Expected call: mock('', bar=4) + E Actual call: mock('fo') + E + E pytest introspection follows: + E + E Args: + E assert ('fo',) == ('',) + E At index 0 diff: 'fo' != '' + E Use -v to get the full diff + E Kwargs: + E assert {} == {'bar': 4} + E Right contains more items: + E {'bar': 4} + E Use -v to get the full diff + + + test_foo.py:6: AssertionError + ========================== 1 failed in 0.03 seconds =========================== + + +This is useful when asserting mock calls with many/nested arguments and trying +to quickly see the difference. + +This feature is probably safe, but if you encounter any problems it can be disabled in +your ``pytest.ini`` file: + +.. code-block:: ini + + [pytest] + mock_traceback_monkeypatch = false + +Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying +mechanism used to suppress traceback entries from ``mock`` module does not work with that option +anyway plus it generates confusing messages on Python 3.5 due to exception chaining + +.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..826c3d8 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,21 @@ + +============ +Contributing +============ + +Contributions are welcome! After cloning the repository, create a virtual env +and install ``pytest-mock`` in editable mode with ``dev`` extras: + +.. code-block:: console + + $ pip install --editable .[dev] + $ pre-commit install + +Tests are run with ``tox``, you can run the baseline environments before submitting a PR: + +.. code-block:: console + + $ tox -e py38,linting + +Style checks and formatting are done automatically during commit courtesy of +`pre-commit `_. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..f60eb42 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,49 @@ +pytest-mock +=========== + +This `pytest`_ plugin provides a ``mocker`` fixture which is a thin-wrapper around the patching API +provided by the `mock package `_: + +.. code-block:: python + + import os + + class UnixFS: + + @staticmethod + def rm(filename): + os.remove(filename) + + def test_unix_fs(mocker): + mocker.patch('os.remove') + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + +Besides undoing the mocking automatically after the end of the test, it also provides other +nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when +comparing calls. + + +Install +======= + +Install using `pip `_: + +.. code-block:: console + + $ pip install pytest-mock + + +.. _`pytest`: https://pytest.org + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + usage + configuration + remarks + contributing + about + changelog diff --git a/docs/remarks.rst b/docs/remarks.rst new file mode 100644 index 0000000..5e20038 --- /dev/null +++ b/docs/remarks.rst @@ -0,0 +1,122 @@ +======= +Remarks +======= + +Type annotations +---------------- + +``pytest-mock`` is fully type annotated, letting users use static type checkers to +test their code. + +The ``mocker`` fixture returns ``pytest_mock.MockerFixture`` which can be used +to annotate test functions: + +.. code-block:: python + + from pytest_mock import MockerFixture + + def test_foo(mocker: MockerFixture) -> None: + ... + +The type annotations have been checked with ``mypy``, which is the only +type checker supported at the moment; other type-checkers might work +but are not currently tested. + + +Why bother with a plugin? +========================= + +There are a number of different ``patch`` usages in the standard ``mock`` API, +but IMHO they don't scale very well when you have more than one or two +patches to apply. + +It may lead to an excessive nesting of ``with`` statements, breaking the flow +of the test: + +.. code-block:: python + + import mock + + def test_unix_fs(): + with mock.patch('os.remove'): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + with mock.patch('os.listdir'): + assert UnixFS.ls('dir') == expected + # ... + + with mock.patch('shutil.copy'): + UnixFS.cp('src', 'dst') + # ... + + +One can use ``patch`` as a decorator to improve the flow of the test: + +.. code-block:: python + + @mock.patch('os.remove') + @mock.patch('os.listdir') + @mock.patch('shutil.copy') + def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + assert UnixFS.ls('dir') == expected + # ... + + UnixFS.cp('src', 'dst') + # ... + +But this poses a few disadvantages: + +- test functions must receive the mock objects as parameter, even if you don't plan to + access them directly; also, order depends on the order of the decorated ``patch`` + functions; +- receiving the mocks as parameters doesn't mix nicely with pytest's approach of + naming fixtures as parameters, or ``pytest.mark.parametrize``; +- you can't easily undo the mocking during the test execution; + +An alternative is to use ``contextlib.ExitStack`` to stack the context managers in a single level of indentation +to improve the flow of the test: + +.. code-block:: python + + import contextlib + import mock + + def test_unix_fs(): + with contextlib.ExitStack() as stack: + stack.enter_context(mock.patch('os.remove')) + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + stack.enter_context(mock.patch('os.listdir')) + assert UnixFS.ls('dir') == expected + # ... + + stack.enter_context(mock.patch('shutil.copy')) + UnixFS.cp('src', 'dst') + # ... + +But this is arguably a little more complex than using ``pytest-mock``. + +Usage as context manager +------------------------ + +Although mocker's API is intentionally the same as ``mock.patch``'s, its use +as context manager and function decorator is **not** supported through the +fixture: + +.. code-block:: python + + def test_context_manager(mocker): + a = A() + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS + assert a.doIt() == True + +The purpose of this plugin is to make the use of context managers and +function decorators for mocking unnecessary, so it will emit a warning when used as such. + +If you really intend to mock a context manager, ``mocker.patch.context_manager`` exists +which won't issue the above warning. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..eed47e0 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +furo +sphinx +sphinx-copybutton diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..f457c74 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,116 @@ +===== +Usage +===== + +The ``mocker`` fixture has the same API as +`mock.patch `_, +supporting the same arguments: + +.. code-block:: python + + def test_foo(mocker): + # all valid calls + mocker.patch('os.remove') + mocker.patch.object(os, 'listdir', autospec=True) + mocked_isfile = mocker.patch('os.path.isfile') + +The supported methods are: + +* `mocker.patch `_ +* `mocker.patch.object `_ +* `mocker.patch.multiple `_ +* `mocker.patch.dict `_ +* `mocker.stopall `_ +* ``mocker.resetall()``: calls `reset_mock() `_ in all mocked objects up to this point. + +Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``: + +* `Mock `_ +* `MagicMock `_ +* `PropertyMock `_ +* `ANY `_ +* `DEFAULT `_ +* `call `_ +* `sentinel `_ +* `mock_open `_ +* `seal `_ + +It is also possible to use mocking functionality from fixtures of other scopes using +the appropriate fixture: + +* ``class_mocker`` +* ``module_mocker`` +* ``package_mocker`` +* ``session_mocker`` + + +Spy +--- + +The ``mocker.spy`` object acts exactly like the original method in all cases, except the spy +also tracks function/method calls, return values and exceptions raised. + +.. code-block:: python + + def test_spy_method(mocker): + class Foo(object): + def bar(self, v): + return v * 2 + + foo = Foo() + spy = mocker.spy(foo, 'bar') + assert foo.bar(21) == 42 + + spy.assert_called_once_with(21) + assert spy.spy_return == 42 + + def test_spy_function(mocker): + # mymodule declares `myfunction` which just returns 42 + import mymodule + + spy = mocker.spy(mymodule, "myfunction") + assert mymodule.myfunction() == 42 + assert spy.call_count == 1 + assert spy.spy_return == 42 + +The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions +are available (like ``assert_called_once_with`` or ``call_count`` in the examples above). + +In addition, spy objects contain two extra attributes: + +* ``spy_return``: contains the returned value of the spied function. +* ``spy_exception``: contain the last exception value raised by the spied function/method when + it was last called, or ``None`` if no exception was raised. + +Besides functions and normal methods, ``mocker.spy`` also works for class and static methods. + +As of version 3.0.0, ``mocker.spy`` also works with ``async def`` functions. + +.. note:: + + In versions earlier than ``2.0``, the attributes were called ``return_value`` and + ``side_effect`` respectively, but due to incompatibilities with ``unittest.mock`` + they had to be renamed (see `#175`_ for details). + + .. _#175: https://github.com/pytest-dev/pytest-mock/issues/175 + +Stub +---- + +The stub is a mock object that accepts any arguments and is useful to test callbacks. +It may receive an optional name that is shown in its ``repr``, useful for debugging. + +.. code-block:: python + + def test_stub(mocker): + def foo(on_something): + on_something('foo', 'bar') + + stub = mocker.stub(name='on_something_stub') + + foo(stub) + stub.assert_called_once_with('foo', 'bar') + +.. seealso:: + + ``async_stub`` method, which actually the same as ``stub`` but makes async stub. diff --git a/src/pytest_mock.egg-info/PKG-INFO b/src/pytest_mock.egg-info/PKG-INFO index db19171..94bd396 100644 --- a/src/pytest_mock.egg-info/PKG-INFO +++ b/src/pytest_mock.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pytest-mock -Version: 3.7.0 +Version: 3.8.2 Summary: Thin-wrapper around the mock package for easier use with pytest Home-page: https://github.com/pytest-dev/pytest-mock/ Author: Bruno Oliveira @@ -51,7 +51,7 @@ Besides undoing the mocking automatically after the end of the test, it also pro nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when comparing calls. -|python| |version| |anaconda| |ci| |coverage| |black| |pre-commit| +|python| |version| |anaconda| |docs| |ci| |coverage| |black| |pre-commit| .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.svg :target: https://pypi.python.org/pypi/pytest-mock @@ -59,7 +59,7 @@ comparing calls. .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-mock.svg :target: https://anaconda.org/conda-forge/pytest-mock -.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/build/badge.svg +.. |ci| image:: https://github.com/pytest-dev/pytest-mock/workflows/test/badge.svg :target: https://github.com/pytest-dev/pytest-mock/actions .. |coverage| image:: https://coveralls.io/repos/github/pytest-dev/pytest-mock/badge.svg?branch=master @@ -74,361 +74,22 @@ comparing calls. .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-mock/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-mock/master -`Professionally supported pytest-mock is now available `_ +.. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest + :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest -Usage -===== -The ``mocker`` fixture has the same API as -`mock.patch `_, -supporting the same arguments: +`Professionally supported pytest-mock is available `_. -.. code-block:: python - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') +Documentation +============= -The supported methods are: - -* `mocker.patch `_ -* `mocker.patch.object `_ -* `mocker.patch.multiple `_ -* `mocker.patch.dict `_ -* `mocker.stopall `_ -* ``mocker.resetall()``: calls `reset_mock() `_ in all mocked objects up to this point. - -Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``: - -* `Mock `_ -* `MagicMock `_ -* `PropertyMock `_ -* `ANY `_ -* `DEFAULT `_ *(Version 1.4)* -* `call `_ *(Version 1.1)* -* `sentinel `_ *(Version 1.2)* -* `mock_open `_ -* `seal `_ *(Version 3.4)* - -It is also possible to use mocking functionality from fixtures of other scopes using -the appropriate mock fixture: - -* ``class_mocker`` -* ``module_mocker`` -* ``package_mocker`` -* ``session_mocker`` - -Type Annotations ----------------- - -*New in version 3.3.0.* - -``pytest-mock`` is fully type annotated, letting users use static type checkers to -test their code. - -The ``mocker`` fixture returns ``pytest_mock.MockerFixture`` which can be used -to annotate test functions: - -.. code-block:: python - - from pytest_mock import MockerFixture - - def test_foo(mocker: MockerFixture) -> None: - ... - -The type annotations have been checked with ``mypy``, which is the only -type checker supported at the moment; other type-checkers might work -but are not currently tested. - -Spy ---- - -The ``mocker.spy`` object acts exactly like the original method in all cases, except the spy -also tracks function/method calls, return values and exceptions raised. - -.. code-block:: python - - def test_spy_method(mocker): - class Foo(object): - def bar(self, v): - return v * 2 - - foo = Foo() - spy = mocker.spy(foo, 'bar') - assert foo.bar(21) == 42 - - spy.assert_called_once_with(21) - assert spy.spy_return == 42 - - def test_spy_function(mocker): - # mymodule declares `myfunction` which just returns 42 - import mymodule - - spy = mocker.spy(mymodule, "myfunction") - assert mymodule.myfunction() == 42 - assert spy.call_count == 1 - assert spy.spy_return == 42 - -The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions -are available (like ``assert_called_once_with`` or ``call_count`` in the examples above). - -In addition, spy objects contain two extra attributes: - -* ``spy_return``: contains the returned value of the spied function. -* ``spy_exception``: contain the last exception value raised by the spied function/method when - it was last called, or ``None`` if no exception was raised. - -Besides functions and normal methods, ``mocker.spy`` also works for class and static methods. - -As of version 3.0.0, ``mocker.spy`` also works with ``async def`` functions. - -.. note:: - - In versions earlier than ``2.0``, the attributes were called ``return_value`` and - ``side_effect`` respectively, but due to incompatibilities with ``unittest.mock`` - they had to be renamed (see `#175`_ for details). - - .. _#175: https://github.com/pytest-dev/pytest-mock/issues/175 - -Stub ----- - -The stub is a mock object that accepts any arguments and is useful to test callbacks. -It may receive an optional name that is shown in its ``repr``, useful for debugging. - -.. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - -Improved reporting of mock call assertion errors ------------------------------------------------- - -This plugin monkeypatches the mock library to improve pytest output for failures -of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback -entries from the ``mock`` module. - -It also adds introspection information on differing call arguments when -calling the helper methods. This features catches `AssertionError` raised in -the method, and uses pytest's own `advanced assertions`_ to return a better -diff:: - - - mocker = - - def test(mocker): - m = mocker.Mock() - m('fo') - > m.assert_called_once_with('', bar=4) - E AssertionError: Expected call: mock('', bar=4) - E Actual call: mock('fo') - E - E pytest introspection follows: - E - E Args: - E assert ('fo',) == ('',) - E At index 0 diff: 'fo' != '' - E Use -v to get the full diff - E Kwargs: - E assert {} == {'bar': 4} - E Right contains more items: - E {'bar': 4} - E Use -v to get the full diff - - - test_foo.py:6: AssertionError - ========================== 1 failed in 0.03 seconds =========================== - - -This is useful when asserting mock calls with many/nested arguments and trying -to quickly see the difference. - -This feature is probably safe, but if you encounter any problems it can be disabled in -your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - -Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying -mechanism used to suppress traceback entries from ``mock`` module does not work with that option -anyway plus it generates confusing messages on Python 3.5 due to exception chaining - -.. _advanced assertions: http://docs.pytest.org/en/stable/assert.html - - -Use standalone "mock" package ------------------------------ - -*New in version 1.4.0.* - -Python 3 users might want to use a newest version of the ``mock`` package as published on PyPI -than the one that comes with the Python distribution. - -.. code-block:: ini - - [pytest] - mock_use_standalone_module = true - -This will force the plugin to import ``mock`` instead of the ``unittest.mock`` module bundled with -Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option -to use the ``mock`` package from PyPI anyway. - -Note about usage as context manager ------------------------------------ - -Although mocker's API is intentionally the same as ``mock.patch``'s, its use -as context manager and function decorator is **not** supported through the -fixture: - -.. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): # DO NOT DO THIS - assert a.doIt() == True - -The purpose of this plugin is to make the use of context managers and -function decorators for mocking unnecessary, so it will emit a warning when used as such. - -If you really intend to mock a context manager, ``mocker.patch.context_manager`` exists -which won't issue the above warning. - - -Install -======= - -Install using `pip `_: - -.. code-block:: console - - $ pip install pytest-mock - -Changelog -========= - -Please consult the `changelog page`_. - -.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - -Why bother with a plugin? -========================= - -There are a number of different ``patch`` usages in the standard ``mock`` API, -but IMHO they don't scale very well when you have more than one or two -patches to apply. - -It may lead to an excessive nesting of ``with`` statements, breaking the flow -of the test: - -.. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - -One can use ``patch`` as a decorator to improve the flow of the test: - -.. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - -But this poses a few disadvantages: - -- test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; -- receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; -- you can't easily undo the mocking during the test execution; - -An alternative is to use ``contextlib.ExitStack`` to stack the context managers in a single level of indentation -to improve the flow of the test: - -.. code-block:: python - - import contextlib - import mock - - def test_unix_fs(): - with contextlib.ExitStack() as stack: - stack.enter_context(mock.patch('os.remove')) - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - stack.enter_context(mock.patch('os.listdir')) - assert UnixFS.ls('dir') == expected - # ... - - stack.enter_context(mock.patch('shutil.copy')) - UnixFS.cp('src', 'dst') - # ... - -But this is arguably a little more complex than using ``pytest-mock``. - -Contributing -============ - -Contributions are welcome! After cloning the repository, create a virtual env -and install ``pytest-mock`` in editable mode with ``dev`` extras: - -.. code-block:: console - - $ pip install --editable .[dev] - $ pre-commit install - -Tests are run with ``tox``, you can run the baseline environments before submitting a PR: - -.. code-block:: console - - $ tox -e py38,linting - -Style checks and formatting are done automatically during commit courtesy of -`pre-commit `_. +For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. -Security contact information -============================ - -To report a security vulnerability, please use the `Tidelift security contact `__. Tidelift will coordinate the fix and disclosure. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE - - diff --git a/src/pytest_mock.egg-info/SOURCES.txt b/src/pytest_mock.egg-info/SOURCES.txt index b1fe832..3b9a296 100644 --- a/src/pytest_mock.egg-info/SOURCES.txt +++ b/src/pytest_mock.egg-info/SOURCES.txt @@ -8,7 +8,17 @@ mypy.ini setup.py tox.ini .github/FUNDING.yml -.github/workflows/main.yml +.github/workflows/deploy.yml +.github/workflows/test.yml +docs/about.rst +docs/changelog.rst +docs/conf.py +docs/configuration.rst +docs/contributing.rst +docs/index.rst +docs/remarks.rst +docs/requirements.txt +docs/usage.rst scripts/gen-release-notes.py src/pytest_mock/__init__.py src/pytest_mock/_util.py diff --git a/src/pytest_mock.egg-info/entry_points.txt b/src/pytest_mock.egg-info/entry_points.txt index abb7b36..c4582bf 100644 --- a/src/pytest_mock.egg-info/entry_points.txt +++ b/src/pytest_mock.egg-info/entry_points.txt @@ -1,3 +1,2 @@ [pytest11] pytest_mock = pytest_mock - diff --git a/src/pytest_mock/_version.py b/src/pytest_mock/_version.py index 8f92aba..ba872d8 100644 --- a/src/pytest_mock/_version.py +++ b/src/pytest_mock/_version.py @@ -1,5 +1,5 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '3.7.0' -version_tuple = (3, 7, 0) +__version__ = version = '3.8.2' +__version_tuple__ = version_tuple = (3, 8, 2) diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index 088e5ce..6b0ce72 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -27,6 +27,11 @@ from ._util import parse_ini_boolean _T = TypeVar("_T") +if sys.version_info[:2] > (3, 7): + AsyncMockType = unittest.mock.AsyncMock +else: + AsyncMockType = Any + class PytestMockWarning(UserWarning): """Base class for all warnings emitted by pytest-mock.""" @@ -159,6 +164,19 @@ class MockerFixture: self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name), ) + def async_stub(self, name: Optional[str] = None) -> AsyncMockType: + """ + Create a async stub method. It accepts any arguments. Ideal to register to + callbacks in tests. + + :param name: the constructed stub's name as used in repr + :return: Stub object. + """ + return cast( + AsyncMockType, + self.mock_module.AsyncMock(spec=lambda *args, **kwargs: None, name=name), + ) + class _Patcher: """ Object to provide the same interface as mock.patch, mock.patch.object, diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index cca50ad..02be474 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -2,6 +2,7 @@ import os import platform import re import sys +import warnings from contextlib import contextmanager from typing import Any from typing import Callable @@ -25,13 +26,16 @@ skip_pypy = pytest.mark.skipif( # Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0 NEW_FORMATTING = sys.version_info >= (3, 8) +if sys.version_info[:2] >= (3, 8): + from unittest.mock import AsyncMock + @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. + Making this a fixture to avoid accessing pytest's config in the global context. """ option = pytestconfig.getoption("assertmode") if option != "rewrite": @@ -232,6 +236,13 @@ class TestMockerStub: def test_failure_message_with_name(self, mocker: MagicMock, name: str) -> None: self.__test_failure_message(mocker, name=name) + @pytest.mark.skipif( + sys.version_info[:2] < (3, 8), + reason="This Python version doesn't have `AsyncMock`.", + ) + def test_async_stub_type(self, mocker: MockerFixture) -> None: + assert isinstance(mocker.async_stub(), AsyncMock) + def test_instance_method_spy(mocker: MockerFixture) -> None: class Foo: @@ -707,10 +718,14 @@ def test_monkeypatch_no_terminal(testdir: Any) -> None: def test_standalone_mock(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used.""" + pytest.importorskip("mock") + testdir.makepyfile( """ + import mock + def test_foo(mocker): - pass + assert mock.MagicMock is mocker.MagicMock """ ) testdir.makeini( @@ -720,8 +735,7 @@ def test_standalone_mock(testdir: Any) -> None: """ ) result = testdir.runpytest_subprocess() - assert result.ret == 3 - result.stderr.fnmatch_lines(["*No module named 'mock'*"]) + assert result.ret == 0 @pytest.mark.usefixtures("needs_assert_rewrite") @@ -758,12 +772,12 @@ def test_detailed_introspection(testdir: Any) -> None: "*Args:", "*assert ('fo',) == ('',)", "*At index 0 diff: 'fo' != ''*", - "*Use -v to get the full diff*", + "*Use -v to get more diff*", "*Kwargs:*", "*assert {} == {'bar': 4}*", "*Right contains* more item*", "*{'bar': 4}*", - "*Use -v to get the full diff*", + "*Use -v to get more diff*", ] result.stdout.fnmatch_lines(expected_lines) @@ -799,12 +813,12 @@ def test_detailed_introspection_async(testdir: Any) -> None: "*Args:", "*assert ('fo',) == ('',)", "*At index 0 diff: 'fo' != ''*", - "*Use -v to get the full diff*", + "*Use -v to get more diff*", "*Kwargs:*", "*assert {} == {'bar': 4}*", "*Right contains* more item*", "*{'bar': 4}*", - "*Use -v to get the full diff*", + "*Use -v to get more diff*", ] result.stdout.fnmatch_lines(expected_lines) @@ -929,7 +943,7 @@ def test_patch_context_manager_with_context_manager(mocker: MockerFixture) -> No a = A() - with pytest.warns(None) as warn_record: + with warnings.catch_warnings(record=True) as warn_record: with mocker.patch.context_manager(a, "doIt", return_value=True): assert a.doIt() is True diff --git a/tox.ini b/tox.ini index 31d7b47..32c93de 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ envlist = py{37,38,39,310}, norewrite passenv = USER USERNAME deps = coverage + mock pytest-asyncio commands = coverage run --append --source={envsitepackagesdir}/pytest_mock -m pytest tests @@ -20,3 +21,10 @@ asyncio_mode = auto [flake8] max-line-length = 88 + +[testenv:docs] +usedevelop = True +deps = + -r docs/requirements.txt +commands = + sphinx-build -W --keep-going -b html docs docs/_build/html {posargs:}