././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1711059169.23833 pytest-mock-3.14.0/0000755000175100001770000000000014577130341013470 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/.github/0000755000175100001770000000000014577130341015030 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.github/FUNDING.yml0000644000175100001770000000030414577130305016642 0ustar00runnerdocker# 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/.github/workflows/0000755000175100001770000000000014577130341017065 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.github/workflows/deploy.yml0000644000175100001770000000320514577130305021104 0ustar00runnerdockername: deploy on: workflow_dispatch: inputs: version: description: 'Release version' required: true default: '1.2.3' jobs: package: runs-on: ubuntu-latest env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - uses: actions/checkout@v3 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v1.5 deploy: needs: package runs-on: ubuntu-latest environment: deploy permissions: id-token: write # For PyPI trusted publishers. contents: write # For tag and release notes. steps: - uses: actions/checkout@v3 - name: Download Package uses: actions/download-artifact@v3 with: name: Packages path: dist - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@v1.8.5 - name: Push tag run: | git config user.name "pytest bot" git config user.email "pytestbot@gmail.com" git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }} git push origin v${{ github.event.inputs.version }} - name: Set up Python uses: actions/setup-python@v4.5.0 with: python-version: "3.10" - 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 files: dist/* tag_name: v${{ github.event.inputs.version }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.github/workflows/test.yml0000644000175100001770000000260114577130305020566 0ustar00runnerdockername: test on: push: branches: - main - "test-me-*" pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: package: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v1.5 test: needs: [package] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python: ["3.8", "3.9", "3.10", "3.11", "3.12"] os: [ubuntu-latest, windows-latest] tox_env: ["py"] include: - python: "3.10" os: ubuntu-latest tox_env: "pytest6" - python: "3.12" os: ubuntu-latest tox_env: "norewrite" - python: "3.12" os: windows-latest tox_env: "norewrite" steps: - uses: actions/checkout@v3 - name: Download Package uses: actions/download-artifact@v3 with: name: Packages path: dist - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install tox run: | python -m pip install --upgrade pip pip install tox - name: Test shell: bash run: | tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.gitignore0000644000175100001770000000122614577130305015461 0ustar00runnerdocker# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg* # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache .pytest_cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation docs/_build/ # Virtual Envs .env* # IDE .idea .vscode /src/pytest_mock/_version.py scripts/latest-release-notes.md ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.pre-commit-config.yaml0000644000175100001770000000120514577130305017747 0ustar00runnerdockerexclude: '^($|.*\.bin)' repos: - repo: local hooks: - id: rst name: rst entry: rst-lint --encoding utf-8 files: ^(CHANGELOG.rst|README.rst|HOWTORELEASE.rst|changelog/.*)$ language: python additional_dependencies: [pygments, restructuredtext_lint] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.3.3 hooks: - id: ruff args: ["--fix"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.9.0 hooks: - id: mypy files: ^(src|tests) args: [] additional_dependencies: [pytest>=6, types-mock] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/.readthedocs.yml0000644000175100001770000000031514577130305016555 0ustar00runnerdockerversion: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/conf.py fail_on_warning: true python: install: - requirements: docs/requirements.txt - path: . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/CHANGELOG.rst0000644000175100001770000005352714577130305015525 0ustar00runnerdockerReleases ======== 3.14.0 (2024-03-21) ------------------- * `#415 `_: ``MockType`` and ``AsyncMockType`` can be imported from ``pytest_mock`` for type annotation purposes. * `#420 `_: Fixed a regression which would cause ``mocker.patch.object`` to not being properly cleared between tests. 3.13.0 (2024-03-21) ------------------- * `#417 `_: ``spy`` now has ``spy_return_list``, which is a list containing all the values returned by the spied function. * ``pytest-mock`` now requires ``pytest>=6.2.5``. * `#410 `_: pytest-mock's ``setup.py`` file is removed. If you relied on this file, e.g. to install pytest using ``setup.py install``, please see `Why you shouldn't invoke setup.py directly `_ for alternatives. 3.12.0 (2023-10-19) ------------------- * Added support for Python 3.12. * Dropped support for EOL Python 3.7. * ``mocker.resetall()`` now also resets mocks created by ``mocker.create_autospec`` (`#390`_). .. _#390: https://github.com/pytest-dev/pytest-mock/pull/390 3.11.1 (2023-06-15) ------------------- (This release source code is identical to ``3.11.0`` except a small internal fix to deployment/CI) * Fixed introspection for failed ``assert_has_calls`` (`#365`_). * Updated type annotations for ``mocker.patch`` and ``mocker.spy`` (`#364`_). .. _#365: https://github.com/pytest-dev/pytest-mock/pull/365 .. _#364: https://github.com/pytest-dev/pytest-mock/pull/364 3.11.0 (2023-06-15) ------------------- * Fixed introspection for failed ``assert_has_calls`` (`#365`_). * Updated type annotations for ``mocker.patch`` and ``mocker.spy`` (`#364`_). .. _#365: https://github.com/pytest-dev/pytest-mock/pull/365 .. _#364: https://github.com/pytest-dev/pytest-mock/pull/364 3.10.0 (2022-10-05) ------------------- * Added new ``mocker.stop(m)`` method to stop specific ``mocker.patch`` or ``mocker.spy`` calls (`#319`_). .. _#319: https://github.com/pytest-dev/pytest-mock/pull/319 3.9.0 (2022-09-28) ------------------ * Expose ``NonCallableMagicMock`` via the ``mocker`` fixture (`#318`_). .. _#318: https://github.com/pytest-dev/pytest-mock/pull/318 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) ------------------ * Python 3.10 now officially supported. * Dropped support for Python 3.6. 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 `__ 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 ------ * Fix plugin when 'terminal' plugin is disabled 1.10.3 ------ * Fix test suite in Python 3.8. Thanks `@hroncok`_ for the report and `@blueyed`_ for the PR (`#140`_). .. _#140: https://github.com/pytest-dev/pytest-mock/pull/140 .. _@hroncok: https://github.com/hroncok 1.10.2 ------ * Fix bug at the end of the test session when a call to ``patch.stopall`` is done explicitly by user code. Thanks `@craiga`_ for the report (`#137`_). .. _#137: https://github.com/pytest-dev/pytest-mock/issues/137 .. _@craiga: https://github.com/craiga 1.10.1 ------ * Fix broken links and update README. Also the code is now formatted using `black `__. 1.10.0 ------ * Add support for the recently added ``assert_called`` method in Python 3.6 and ``mock-2.0``. Thanks `@rouge8`_ for the PR (`#115`_). .. _#115: https://github.com/pytest-dev/pytest-mock/pull/115 1.9.0 ----- * Add support for the recently added ``assert_called_once`` method in Python 3.6 and ``mock-2.0``. Thanks `@rouge8`_ for the PR (`#113`_). .. _#113: https://github.com/pytest-dev/pytest-mock/pull/113 1.8.0 ----- * Add aliases for ``NonCallableMock`` and ``create_autospec`` to ``mocker``. Thanks `@mlhamel`_ for the PR (`#111`_). .. _#111: https://github.com/pytest-dev/pytest-mock/pull/111 1.7.1 ----- * Fix ``setup.py`` to correctly read the ``README.rst``. Thanks `@ghisvail`_ for the fix (`#107`_). .. _#107: https://github.com/pytest-dev/pytest-mock/issues/107 1.7.0 ----- **Incompatible change** * ``pytest-mock`` no longer supports Python 2.6 and Python 3.3, following the lead of ``pytest`` and other projects in the community. Thanks `@hugovk`_ for the PR (`#96`_). **Packaging** * Fix ``mock`` requirement in Python 2. Thanks `@ghisvail`_ for the report (`#101`_). **Internal** * Some tests in ``pytest-mock``'s suite are skipped if assertion rewriting is disabled (`#102`_). .. _@ghisvail: https://github.com/ghisvail .. _@hugovk: https://github.com/hugovk .. _#96: https://github.com/pytest-dev/pytest-mock/pull/96 .. _#101: https://github.com/pytest-dev/pytest-mock/issues/101 .. _#102: https://github.com/pytest-dev/pytest-mock/issues/102 1.6.3 ----- * Fix ``UnicodeDecodeError`` during assert introspection in ``assert_called_with`` in Python 2. Thanks `@AndreasHogstrom`_ for the report (`#91`_). .. _@AndreasHogstrom: https://github.com/AndreasHogstrom .. _#91: https://github.com/pytest-dev/pytest-mock/issues/91 1.6.2 ----- * Provide source package in ``tar.gz`` format and remove obsolete ``MANIFEST.in``. 1.6.1 ----- * Fix ``mocker.resetall()`` by ignoring mocker objects which don't have a ``resetall`` method, like for example ``patch.dict``. Thanks `@jdavisp3`_ for the PR (`#88`_). .. _@jdavisp3: https://github.com/jdavisp3 .. _#88: https://github.com/pytest-dev/pytest-mock/pull/88 1.6.0 ----- * The original assertions raised by the various ``Mock.assert_*`` methods now appear in the failure message, in addition to the message obtained from pytest introspection. Thanks `@quodlibetor`_ for the initial patch (`#79`_). .. _@quodlibetor: https://github.com/quodlibetor .. _#79: https://github.com/pytest-dev/pytest-mock/pull/79 1.5.0 ----- * New ``mocker.mock_module`` variable points to the underlying mock module being used (``unittest.mock`` or ``mock``). Thanks `@blueyed`_ for the request (`#71`_). .. _#71: https://github.com/pytest-dev/pytest-mock/pull/71 1.4.0 ----- * New configuration variable, ``mock_use_standalone_module`` (defaults to ``False``). This forces the plugin to import ``mock`` instead of ``unittest.mock`` on Python 3. This is useful to import a newer version than the one available in the Python distribution. * Previously the plugin would first try to import ``mock`` and fallback to ``unittest.mock`` in case of an ``ImportError``, but this behavior has been removed because it could hide hard to debug import errors (`#68`_). * Now ``mock`` (Python 2) and ``unittest.mock`` (Python 3) are lazy-loaded to make it possible to implement the new ``mock_use_standlone_module`` configuration option. As a consequence of this the undocumented ``pytest_mock.mock_module`` variable, which pointed to the actual mock module being used by the plugin, has been removed. * `DEFAULT `_ is now available from the ``mocker`` fixture. .. _#68: https://github.com/pytest-dev/pytest-mock/issues/68 1.3.0 ----- * Add support for Python 3.6. Thanks `@hackebrot`_ for the report (`#59`_). * ``mock.mock_open`` is now aliased as ``mocker.mock_open`` for convenience. Thanks `@pokidovea`_ for the PR (`#66`_). .. _@hackebrot: https://github.com/hackebrot .. _@pokidovea: https://github.com/pokidovea .. _#59: https://github.com/pytest-dev/pytest-mock/issues/59 .. _#66: https://github.com/pytest-dev/pytest-mock/pull/66 1.2 --- * Try to import ``mock`` first instead of ``unittest.mock``. This gives the user flexibility to install a newer ``mock`` version from PyPI instead of using the one available in the Python distribution. Thanks `@wcooley`_ for the PR (`#54`_). * ``mock.sentinel`` is now aliased as ``mocker.sentinel`` for convenience. Thanks `@kjwilcox`_ for the PR (`#56`_). .. _@wcooley: https://github.com/wcooley .. _@kjwilcox: https://github.com/kjwilcox .. _#54: https://github.com/pytest-dev/pytest-mock/issues/54 .. _#56: https://github.com/pytest-dev/pytest-mock/pull/56 1.1 --- * From this version onward, ``pytest-mock`` is licensed under the `MIT`_ license (`#45`_). * Now the plugin also adds introspection information on differing call arguments when calling helper methods such as ``assert_called_once_with``. The extra introspection information is similar to pytest's and can be disabled with the ``mock_traceback_monkeypatch`` option. Thanks `@asfaltboy`_ for the PR (`#36`_). * ``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. Thanks `@jurko-gospodnetic`_ for the PR (`#40`_). * Monkey patching ``mock`` module for friendlier tracebacks 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 (`#44`_). Thanks `@blueyed`_ for the report. * ``mock.call`` is now aliased as ``mocker.call`` for convenience. Thanks `@jhermann`_ for the PR (`#49`_). .. _@jurko-gospodnetic: https://github.com/jurko-gospodnetic .. _@asfaltboy: https://github.com/asfaltboy .. _@jhermann: https://github.com/jhermann .. _#45: https://github.com/pytest-dev/pytest-mock/issues/45 .. _#36: https://github.com/pytest-dev/pytest-mock/issues/36 .. _#40: https://github.com/pytest-dev/pytest-mock/issues/40 .. _#44: https://github.com/pytest-dev/pytest-mock/issues/44 .. _#49: https://github.com/pytest-dev/pytest-mock/issues/49 .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE 1.0 --- * Fix AttributeError with ``mocker.spy`` when spying on inherited methods (`#42`_). Thanks `@blueyed`_ for the PR. .. _@blueyed: https://github.com/blueyed .. _#42: https://github.com/pytest-dev/pytest-mock/issues/42 0.11.0 ------ * `PropertyMock `_ is now accessible from ``mocker``. Thanks `@satyrius`_ for the PR (`#32`_). * Fix regression using one of the ``assert_*`` methods in patched functions which receive a parameter named ``method``. Thanks `@sagarchalise`_ for the report (`#31`_). .. _@sagarchalise: https://github.com/sagarchalise .. _@satyrius: https://github.com/satyrius .. _#31: https://github.com/pytest-dev/pytest-mock/issues/31 .. _#32: https://github.com/pytest-dev/pytest-mock/issues/32 0.10.1 ------ * Fix regression in frozen tests due to ``distutils`` import dependency. Thanks `@The-Compiler`_ for the report (`#29`_). * Fix regression when using ``pytest-mock`` with ``pytest-2.7.X``. Thanks `@akscram`_ for the report (`#28`_). .. _@akscram: https://github.com/Chronial .. _#28: https://github.com/pytest-dev/pytest-mock/issues/28 .. _#29: https://github.com/pytest-dev/pytest-mock/issues/29 0.10 ---- * ``pytest-mock`` now monkeypatches the ``mock`` library to improve pytest output for failures of mock call assertions like ``Mock.assert_called_with()``. Thanks to `@Chronial`_ for idea and PR (`#26`_, `#27`_)! .. _@Chronial: https://github.com/Chronial .. _#26: https://github.com/pytest-dev/pytest-mock/issues/26 .. _#27: https://github.com/pytest-dev/pytest-mock/issues/27 0.9.0 ----- * New ``mocker.resetall`` function, which calls ``reset_mock()`` in all mocked objects up to that point. Thanks to `@mathrick`_ for the PR! 0.8.1 ----- * ``pytest-mock`` is now also available as a wheel. Thanks `@rouge8`_ for the PR! 0.8.0 ----- * ``mock.ANY`` is now accessible from the mocker fixture (`#17`_), thanks `@tigarmo`_ for the PR! .. _#17: https://github.com/pytest-dev/pytest-qt/issues/17 0.7.0 ----- Thanks to `@fogo`_, mocker.spy can now prey upon staticmethods and classmethods. :smile: 0.6.0 ----- * Two new auxiliary methods, ``spy`` and ``stub``. See ``README`` for usage. (Thanks `@fogo`_ for complete PR!) 0.5.0 ----- * ``Mock`` and ``MagicMock`` are now accessible from the ``mocker`` fixture, many thanks to `@marcwebbie`_ for the complete PR! 0.4.3 ----- * ``mocker`` fixture now returns the same object (`#8`_). Many thanks to `@RonnyPfannschmidt`_ for the PR! .. _#8: https://github.com/pytest-dev/pytest-qt/issues/8 0.4.2 ----- * Small fix, no longer using wheel as an alternate package since it conditionally depends on mock module based on Python version, as Python >= 3.3 already includes ``unittest.mock``. Many thanks to `@The-Compiler`_ for letting me know and providing a PR with the fix! 0.4.1 ----- * Small release that just uses ``pytest_mock`` as the name of the plugin, instead of ``pytest-mock``: this makes it simple to depend on this plugin explicitly using ``pytest_plugins`` module variable mechanism. 0.4.0 ----- * Changed fixture name from ``mock`` into ``mocker`` because it conflicted with the actual mock module, which made using it awkward when access to both the module and the fixture were required within a test. Thanks `@kmosher`_ for request and discussion in `#4`_. :smile: .. _#4: https://github.com/pytest-dev/pytest-qt/issues/4 0.3.0 ----- * Fixed bug `#2`_, where a patch would not be uninstalled correctly after patching the same object twice. 0.2.0 ----- * Added ``patch.dict`` support. 0.1.0 ----- First release. .. _#2: https://github.com/pytest-dev/pytest-qt/issues/2 .. _@fogo: https://github.com/fogo .. _@kmosher: https://github.com/kmosher .. _@marcwebbie: https://github.com/marcwebbie .. _@mathrick: https://github.com/mathrick .. _@mlhamel: https://github.com/mlhamel .. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt .. _@rouge8: https://github.com/rouge8 .. _@The-Compiler: https://github.com/The-Compiler .. _@tigarmo: https://github.com/tigarmo ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/LICENSE0000644000175100001770000000206314577130305014476 0ustar00runnerdockerMIT License Copyright (c) [2016] [Bruno Oliveira] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1711059169.23833 pytest-mock-3.14.0/PKG-INFO0000644000175100001770000000736114577130341014574 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-mock Version: 3.14.0 Summary: Thin-wrapper around the mock package for easier use with pytest Author-email: Bruno Oliveira License: MIT Project-URL: Homepage, https://github.com/pytest-dev/pytest-mock/ Project-URL: Documentation, https://pytest-mock.readthedocs.io/en/latest/ Project-URL: Changelog, https://pytest-mock.readthedocs.io/en/latest/changelog.html Project-URL: Source, https://github.com/pytest-dev/pytest-mock/ Project-URL: Tracker, https://github.com/pytest-dev/pytest-mock/issues Keywords: pytest,mock Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pytest>=6.2.5 Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" Requires-Dist: pytest-asyncio; extra == "dev" Requires-Dist: tox; extra == "dev" =========== pytest-mock =========== This 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. |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 .. |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/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 :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/ .. |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 .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest `Professionally supported pytest-mock is available `_. Documentation ============= For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/README.rst0000644000175100001770000000446314577130305015166 0ustar00runnerdocker=========== pytest-mock =========== This 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. |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 .. |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/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 :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/ .. |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 .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest `Professionally supported pytest-mock is available `_. Documentation ============= For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/RELEASING.rst0000644000175100001770000000057714577130305015544 0ustar00runnerdockerHere are the steps on how to make a new release. 1. Create a ``release-VERSION`` branch from ``upstream/main``. 2. Update ``CHANGELOG.rst``. 3. Push the branch to ``upstream``. 4. Once all tests pass, start the ``deploy`` workflow manually or via: ``` gh workflow run deploy.yml --repo pytest-dev/pytest-mock --ref release-VERSION -f version=VERSION ``` 5. Merge the PR. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/SECURITY.md0000644000175100001770000000050714577130305015263 0ustar00runnerdocker## Security contact information This project is not used in production, so there should be no security concerns. However as part of the Tidelift offering, the policy to report a security vulnerability is to use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/docs/0000755000175100001770000000000014577130341014420 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/docs/_static/0000755000175100001770000000000014577130341016046 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/_static/.gitignore0000644000175100001770000000001614577130305020033 0ustar00runnerdocker* !.gitignore ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/about.rst0000644000175100001770000000116714577130305016271 0ustar00runnerdocker===== 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/changelog.rst0000644000175100001770000000011114577130305017072 0ustar00runnerdocker========= Changelog ========= :hide-toc: .. include:: ../CHANGELOG.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/conf.py0000644000175100001770000000356314577130305015726 0ustar00runnerdocker# 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"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/configuration.rst0000644000175100001770000000452414577130305020026 0ustar00runnerdocker============= 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/contributing.rst0000644000175100001770000000101514577130305017656 0ustar00runnerdocker ============ 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 Style checks and formatting are done automatically during commit courtesy of `pre-commit `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/index.rst0000644000175100001770000000167114577130305016266 0ustar00runnerdockerpytest-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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/remarks.rst0000644000175100001770000000550414577130305016622 0ustar00runnerdocker======= 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``. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/requirements.txt0000644000175100001770000000003614577130305017703 0ustar00runnerdockerfuro sphinx sphinx-copybutton ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/docs/usage.rst0000644000175100001770000001400714577130305016260 0ustar00runnerdocker===== 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.stop `_ * ``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 last returned value of the spied function. * ``spy_return_list``: contains a list of all returned values of the spied function (new in ``3.13``). * ``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 As of version 3.10, spying can be also selectively stopped. .. code-block:: python def test_with_unspy(mocker): class Foo: def bar(self): return 42 spy = mocker.spy(Foo, "bar") foo = Foo() assert foo.bar() == 42 assert spy.call_count == 1 mocker.stop(spy) assert foo.bar() == 42 assert spy.call_count == 1 ``mocker.stop()`` can also be used by ``mocker.patch`` calls. 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. 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. Where to patch -------------- A common issue where mocking appears not to be working is patching in the wrong place. See this `section in the unittest docs `__ which provides a comprehensive explanation. Also see this excellent blog post: `Why your mock doesn't work `__. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/mypy.ini0000644000175100001770000000053414577130305015171 0ustar00runnerdocker[mypy] disallow_any_generics = True disallow_incomplete_defs = True disallow_subclassing_any = True ignore_missing_imports = 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/pyproject.toml0000644000175100001770000000336614577130305016414 0ustar00runnerdocker[build-system] requires = [ "setuptools", "setuptools-scm[toml]", ] build-backend = "setuptools.build_meta" [project] name = "pytest-mock" description = "Thin-wrapper around the mock package for easier use with pytest" authors = [ {name = "Bruno Oliveira", email = "nicoddemus@gmail.com"}, ] dependencies = [ "pytest>=6.2.5", ] dynamic = ["version"] requires-python = ">=3.8" readme = "README.rst" license = {text = "MIT"} keywords = ["pytest", "mock"] classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: Pytest", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Testing", ] [project.urls] Homepage = "https://github.com/pytest-dev/pytest-mock/" Documentation = "https://pytest-mock.readthedocs.io/en/latest/" Changelog = "https://pytest-mock.readthedocs.io/en/latest/changelog.html" Source = "https://github.com/pytest-dev/pytest-mock/" Tracker = "https://github.com/pytest-dev/pytest-mock/issues" [project.optional-dependencies] dev = [ "pre-commit", "pytest-asyncio", "tox", ] [project.entry-points.pytest11] pytest_mock = "pytest_mock" [tool.setuptools.package-data] pytest_mock = ["py.typed"] [tool.setuptools_scm] write_to = "src/pytest_mock/_version.py" [tool.ruff.lint] extend-select = ["I001"] [tool.ruff.lint.isort] force-single-line = true known-third-party = ["src"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/scripts/0000755000175100001770000000000014577130341015157 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/scripts/gen-release-notes.py0000644000175100001770000000252514577130305021052 0ustar00runnerdocker""" Generates the release notes for the latest release, in Markdown. 1. Extracts the latest release from the CHANGELOG.rst file. 2. Converts it to Markdown using pypandoc. 3. Writes to ``scripts/latest-release-notes.md``, which can be used with https://github.com/softprops/action-gh-release. """ from pathlib import Path import pypandoc this_dir = Path(__file__).parent rst_text = (this_dir.parent / "CHANGELOG.rst").read_text(encoding="UTF-8") output_lines = [] capture = False for line in rst_text.splitlines(): # Only start capturing after the latest release section. if line.startswith("-------"): capture = not capture if not capture: # We only need to capture the latest release, so stop. break continue if capture: output_lines.append(line) # Drop last line, as it contains the previous release section title. del output_lines[-1] trimmed_rst = "\n".join(output_lines).strip() print(">>Trimmed RST follows:") print(trimmed_rst) print(">>Trimmed RST ends") md_text = pypandoc.convert_text( trimmed_rst, "md", format="rst", extra_args=["--wrap=preserve"] ) print(">>Converted Markdown follows:") print(md_text) print(">>Converted Markdown ends") output_fn = this_dir / "latest-release-notes.md" output_fn.write_text(md_text, encoding="UTF-8") print(output_fn, "generated.") ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1711059169.23833 pytest-mock-3.14.0/setup.cfg0000644000175100001770000000004614577130341015311 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2303298 pytest-mock-3.14.0/src/0000755000175100001770000000000014577130341014257 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1711059169.2343297 pytest-mock-3.14.0/src/pytest_mock/0000755000175100001770000000000014577130341016620 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/src/pytest_mock/__init__.py0000644000175100001770000000147114577130305020734 0ustar00runnerdockerfrom pytest_mock.plugin import AsyncMockType from pytest_mock.plugin import MockerFixture from pytest_mock.plugin import MockType from pytest_mock.plugin import PytestMockWarning from pytest_mock.plugin import class_mocker from pytest_mock.plugin import mocker 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 session_mocker MockFixture = MockerFixture # backward-compatibility only (#204) __all__ = [ "AsyncMockType", "MockerFixture", "MockFixture", "MockType", "PytestMockWarning", "pytest_addoption", "pytest_configure", "session_mocker", "package_mocker", "module_mocker", "class_mocker", "mocker", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/src/pytest_mock/_util.py0000644000175100001770000000164214577130305020311 0ustar00runnerdockerfrom 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock/_version.py0000644000175100001770000000063514577130341021022 0ustar00runnerdocker# file generated by setuptools_scm # don't change, don't track in version control TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple, Union VERSION_TUPLE = Tuple[Union[int, str], ...] else: VERSION_TUPLE = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE __version__ = version = '3.14.0' __version_tuple__ = version_tuple = (3, 14, 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/src/pytest_mock/plugin.py0000644000175100001770000006033214577130305020474 0ustar00runnerdockerimport asyncio import builtins import functools import inspect import unittest.mock import warnings from dataclasses import dataclass from dataclasses import field from typing import Any from typing import Callable from typing import Dict from typing import Generator from typing import Iterable from typing import Iterator from typing import List from typing import Mapping from typing import Optional from typing import Tuple from typing import Type from typing import TypeVar from typing import Union from typing import cast from typing import overload import pytest from ._util import get_mock_module from ._util import parse_ini_boolean _T = TypeVar("_T") AsyncMockType = unittest.mock.AsyncMock MockType = Union[ unittest.mock.MagicMock, unittest.mock.AsyncMock, unittest.mock.NonCallableMagicMock, ] class PytestMockWarning(UserWarning): """Base class for all warnings emitted by pytest-mock.""" @dataclass class MockCacheItem: mock: MockType patch: Optional[Any] = None @dataclass class MockCache: """ Cache MagicMock and Patcher instances so we can undo them later. """ cache: List[MockCacheItem] = field(default_factory=list) def _find(self, mock: MockType) -> MockCacheItem: for mock_item in self.cache: if mock_item.mock is mock: return mock_item raise ValueError("This mock object is not registered") def add(self, mock: MockType, **kwargs: Any) -> MockCacheItem: self.cache.append(MockCacheItem(mock=mock, **kwargs)) return self.cache[-1] def remove(self, mock: MockType) -> None: mock_item = self._find(mock) if mock_item.patch: mock_item.patch.stop() self.cache.remove(mock_item) def clear(self) -> None: for mock_item in reversed(self.cache): if mock_item.patch is not None: mock_item.patch.stop() self.cache.clear() def __iter__(self) -> Iterator[MockCacheItem]: return iter(self.cache) 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._mock_cache: MockCache = MockCache() self.mock_module = mock_module = get_mock_module(config) self.patch = self._Patcher(self._mock_cache, mock_module) # type: MockerFixture._Patcher # aliases for convenience self.Mock = mock_module.Mock self.MagicMock = mock_module.MagicMock self.NonCallableMock = mock_module.NonCallableMock self.NonCallableMagicMock = mock_module.NonCallableMagicMock 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.sentinel = mock_module.sentinel self.mock_open = mock_module.mock_open if hasattr(mock_module, "seal"): self.seal = mock_module.seal def create_autospec( self, spec: Any, spec_set: bool = False, instance: bool = False, **kwargs: Any ) -> MockType: m: MockType = self.mock_module.create_autospec( spec, spec_set, instance, **kwargs ) self._mock_cache.add(m) return m 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 mock_item in self._mock_cache: # See issue #237. if not hasattr(mock_item.mock, "reset_mock"): continue # NOTE: The mock may be a dictionary if hasattr(mock_item.mock, "spy_return_list"): mock_item.mock.spy_return_list = [] if isinstance(mock_item.mock, supports_reset_mock_with_args): mock_item.mock.reset_mock( return_value=return_value, side_effect=side_effect ) else: mock_item.mock.reset_mock() def stopall(self) -> None: """ Stop all patchers started by this fixture. Can be safely called multiple times. """ self._mock_cache.clear() def stop(self, mock: unittest.mock.MagicMock) -> None: """ Stops a previous patch or spy call by passing the ``MagicMock`` object returned by it. """ self._mock_cache.remove(mock) def spy(self, obj: object, name: str) -> MockType: """ 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) 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 spy_obj.spy_return_list.append(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 spy_obj.spy_return_list.append(r) return r if asyncio.iscoroutinefunction(method): wrapped = functools.update_wrapper(async_wrapper, method) else: wrapped = functools.update_wrapper(wrapper, method) autospec = inspect.ismethod(method) or inspect.isfunction(method) spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec) spy_obj.spy_return = None spy_obj.spy_return_list = [] 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), ) 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, etc. We need this indirection to keep the same API of the mock package. """ DEFAULT = object() def __init__(self, mock_cache, mock_module): self.__mock_cache = mock_cache self.mock_module = mock_module def _start_patch( self, mock_func: Any, warn_on_mock_enter: bool, *args: Any, **kwargs: Any ) -> MockType: """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: MockType = p.start() self.__mock_cache.add(mock=mocked, patch=p) if hasattr(mocked, "reset_mock"): # 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: 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://pytest-mock.readthedocs.io/en/latest/remarks.html#usage-as-context-manager", PytestMockWarning, stacklevel=5, ) 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, ) -> MockType: """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, ) -> MockType: """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, MockType]: """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, ) -> MockType: ... @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 assert_has_calls_wrapper( __wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any ) -> None: __tracebackhide__ = True try: __wrapped_mock_method__(*args, **kwargs) return except AssertionError as e: any_order = kwargs.get("any_order", False) if getattr(e, "_mock_introspection_applied", 0) or any_order: msg = str(e) else: __mock_self = args[0] msg = str(e) if __mock_self.call_args_list is not None: actual_calls = list(__mock_self.call_args_list) expect_calls = args[1] introspection = "" from itertools import zip_longest for actual_call, expect_call in zip_longest(actual_calls, expect_calls): if actual_call is not None: actual_args, actual_kwargs = actual_call else: actual_args = tuple() actual_kwargs = {} if expect_call is not None: _, expect_args, expect_kwargs = expect_call else: expect_args = tuple() expect_kwargs = {} try: assert actual_args == expect_args except AssertionError as e_args: introspection += "\nArgs:\n" + str(e_args) try: assert actual_kwargs == expect_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_has_calls_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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/src/pytest_mock/py.typed0000644000175100001770000000000014577130305020305 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1711059169.23833 pytest-mock-3.14.0/src/pytest_mock.egg-info/0000755000175100001770000000000014577130341020312 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/PKG-INFO0000644000175100001770000000736114577130341021416 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-mock Version: 3.14.0 Summary: Thin-wrapper around the mock package for easier use with pytest Author-email: Bruno Oliveira License: MIT Project-URL: Homepage, https://github.com/pytest-dev/pytest-mock/ Project-URL: Documentation, https://pytest-mock.readthedocs.io/en/latest/ Project-URL: Changelog, https://pytest-mock.readthedocs.io/en/latest/changelog.html Project-URL: Source, https://github.com/pytest-dev/pytest-mock/ Project-URL: Tracker, https://github.com/pytest-dev/pytest-mock/issues Keywords: pytest,mock Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pytest>=6.2.5 Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" Requires-Dist: pytest-asyncio; extra == "dev" Requires-Dist: tox; extra == "dev" =========== pytest-mock =========== This 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. |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 .. |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/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 :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/ .. |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 .. |docs| image:: https://readthedocs.org/projects/pytest-mock/badge/?version=latest :target: https://pytest-mock.readthedocs.io/en/latest/?badge=latest `Professionally supported pytest-mock is available `_. Documentation ============= For full documentation, please see https://pytest-mock.readthedocs.io/en/latest. License ======= Distributed under the terms of the `MIT`_ license. .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/SOURCES.txt0000644000175100001770000000147214577130341022202 0ustar00runnerdocker.gitignore .pre-commit-config.yaml .readthedocs.yml CHANGELOG.rst LICENSE README.rst RELEASING.rst SECURITY.md mypy.ini pyproject.toml tox.ini .github/FUNDING.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 docs/_static/.gitignore scripts/gen-release-notes.py 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././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/dependency_links.txt0000644000175100001770000000000114577130341024360 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/entry_points.txt0000644000175100001770000000004514577130341023607 0ustar00runnerdocker[pytest11] pytest_mock = pytest_mock ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/requires.txt0000644000175100001770000000006314577130341022711 0ustar00runnerdockerpytest>=6.2.5 [dev] pre-commit pytest-asyncio tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059169.0 pytest-mock-3.14.0/src/pytest_mock.egg-info/top_level.txt0000644000175100001770000000001414577130341023037 0ustar00runnerdockerpytest_mock ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1711059169.23833 pytest-mock-3.14.0/tests/0000755000175100001770000000000014577130341014632 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/tests/test_pytest_mock.py0000644000175100001770000010747214577130305020617 0ustar00runnerdockerimport os import platform import re import sys import warnings 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 AsyncMock 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.11.7 changed the output formatting, https://github.com/python/cpython/issues/111019 NEWEST_FORMATTING = sys.version_info >= (3, 11, 7) @pytest.fixture def needs_assert_rewrite(pytestconfig): """ Fixture which skips requesting test if assertion rewrite is disabled (#102) Making this a fixture to avoid accessing 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) class TestObject: """ Class that is used for testing create_autospec with child mocks """ def run(self) -> str: return "not mocked" @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", "MagicMock", "Mock", "mock_open", "NonCallableMagicMock", "NonCallableMock", "PropertyMock", "sentinel", "seal", ], ) 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"]) mocked_object = mocker.create_autospec(TestObject) mocked_object.run.return_value = "mocked" assert listdir("/tmp") == "foo" assert open("/tmp/foo.txt") == "bar" assert mocked_object.run() == "mocked" listdir.assert_called_once_with("/tmp") open.assert_called_once_with("/tmp/foo.txt") mocked_object.run.assert_called_once() mocker.resetall() assert not listdir.called assert not open.called assert not mocked_object.called assert listdir.return_value == "foo" assert list(open.side_effect) == ["baz"] assert mocked_object.run.return_value == "mocked" mocker.resetall(return_value=True, side_effect=True) assert isinstance(listdir.return_value, mocker.Mock) assert open.side_effect is None if sys.version_info >= (3, 9): # The reset on child mocks have been implemented in 3.9 # https://bugs.python.org/issue38932 assert mocked_object.run.return_value != "mocked" 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 f"name={test_name!r}" in repr(stub) def __test_failure_message(self, mocker: MockerFixture, **kwargs: Any) -> None: expected_name = kwargs.get("name") or "mock" if NEWEST_FORMATTING: msg = "expected call not found.\nExpected: {0}()\n Actual: not called." else: msg = "expected call not found.\nExpected: {0}()\nActual: not called." expected_message = msg.format(expected_name) stub = mocker.stub(**kwargs) with pytest.raises(AssertionError, match=re.escape(expected_message)): stub.assert_called_with() 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_async_stub_type(self, mocker: MockerFixture) -> None: assert isinstance(mocker.async_stub(), AsyncMock) 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] assert foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert foo.bar(arg=11) == 22 assert foo.bar(arg=12) == 24 assert spy.spy_return == 24 assert spy.spy_return_list == [20, 22, 24] # 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(f"Error with {arg}") foo = Foo() spy = mocker.spy(foo, "bar") expected_calls = [] for i, v in enumerate([10, 20]): with pytest.raises(exc_cls, match=f"Error with {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) == f"Error with {v}" def test_instance_class_static_method_spy_autospec_true(mocker: MockerFixture) -> None: class Foo: def bar(self, arg): return arg * 2 @classmethod def baz(cls, arg): return arg * 2 @staticmethod def qux(arg): return arg * 2 foo = Foo() instance_method_spy = mocker.spy(foo, "bar") with pytest.raises( AttributeError, match="'function' object has no attribute 'fake_assert_method'" ): instance_method_spy.fake_assert_method(arg=5) class_method_spy = mocker.spy(Foo, "baz") with pytest.raises( AttributeError, match="Mock object has no attribute 'fake_assert_method'" ): class_method_spy.fake_assert_method(arg=5) static_method_spy = mocker.spy(Foo, "qux") with pytest.raises( AttributeError, match="Mock object has no attribute 'fake_assert_method'" ): static_method_spy.fake_assert_method(arg=5) def test_spy_reset(mocker: MockerFixture) -> None: class Foo: 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_return_list == [] assert spy.spy_exception is None Foo().bar(10) assert spy.spy_return == 30 assert spy.spy_return_list == [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 spy.spy_return_list == [] assert str(spy.spy_exception) == "invalid x" Foo().bar(15) assert spy.spy_return == 45 assert spy.spy_return_list == [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 assert spy.spy_return_list == [20, 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] assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert spy.spy_return_list == [20] @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] assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert spy.spy_return_list == [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] assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert spy.spy_return_list == [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] assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert spy.spy_return_list == [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] assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 assert spy.spy_return_list == [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 assert spy.spy_return_list == [20] 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) if int(pytest.__version__.split(".")[0]) < 8: expected = "\n ".join(util._compare_eq_iterable(left, right, verbose)) # type:ignore[arg-type] else: expected = "\n ".join( util._compare_eq_iterable(left, right, lambda t, *_: t, 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_assert_has_calls_multiple_calls(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub("bar") stub("baz") stub.assert_has_calls([mocker.call("foo"), mocker.call("bar"), mocker.call("baz")]) with assert_traceback(): stub.assert_has_calls( [ mocker.call("foo"), mocker.call("bar"), mocker.call("baz"), mocker.call("bat"), ] ) with assert_traceback(): stub.assert_has_calls( [mocker.call("foo"), mocker.call("baz"), mocker.call("bar")] ) def test_assert_has_calls_multiple_calls_subset(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub("bar") stub("baz") stub.assert_has_calls([mocker.call("bar"), mocker.call("baz")]) with assert_traceback(): stub.assert_has_calls([mocker.call("foo"), mocker.call("baz")]) with assert_traceback(): stub.assert_has_calls( [mocker.call("foo"), mocker.call("bar"), mocker.call("bat")] ) with assert_traceback(): stub.assert_has_calls([mocker.call("baz"), mocker.call("bar")]) def test_assert_has_calls_multiple_calls_any_order(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub("bar") stub("baz") stub.assert_has_calls( [mocker.call("foo"), mocker.call("baz"), mocker.call("bar")], any_order=True ) with assert_traceback(): stub.assert_has_calls( [ mocker.call("foo"), mocker.call("baz"), mocker.call("bar"), mocker.call("bat"), ], any_order=True, ) def test_assert_has_calls_multiple_calls_any_order_subset( mocker: MockerFixture, ) -> None: stub = mocker.stub() stub("foo") stub("bar") stub("baz") stub.assert_has_calls([mocker.call("baz"), mocker.call("foo")], any_order=True) with assert_traceback(): stub.assert_has_calls( [mocker.call("baz"), mocker.call("foo"), mocker.call("bat")], any_order=True ) def test_assert_has_calls_no_calls( mocker: MockerFixture, ) -> None: stub = mocker.stub() stub.assert_has_calls([]) with assert_traceback(): stub.assert_has_calls([mocker.call("foo")]) 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( """ 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.""" pytest.importorskip("mock") testdir.makepyfile( """ import mock def test_foo(mocker): assert mock.MagicMock is mocker.MagicMock """ ) testdir.makeini( """ [pytest] mock_use_standalone_module = true """ ) result = testdir.runpytest_subprocess() assert result.ret == 0 @pytest.mark.usefixtures("needs_assert_rewrite") def test_detailed_introspection(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used.""" testdir.makeini( """ [pytest] asyncio_mode=auto """ ) testdir.makepyfile( """ def test(mocker): m = mocker.Mock() m('fo') m.assert_called_once_with('', bar=4) """ ) result = testdir.runpytest("-s") expected_lines = [ "*AssertionError: expected call not found.", "*Expected: mock('', bar=4)", "*Actual: mock('fo')", ] expected_lines += [ "*pytest introspection follows:*", "*Args:", "*assert ('fo',) == ('',)", "*At index 0 diff: 'fo' != ''*", "*Use -v to*", "*Kwargs:*", "*assert {} == {'bar': 4}*", "*Right contains* more item*", "*{'bar': 4}*", "*Use -v to*", ] result.stdout.fnmatch_lines(expected_lines) @pytest.mark.usefixtures("needs_assert_rewrite") def test_detailed_introspection_async(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used.""" testdir.makeini( """ [pytest] asyncio_mode=auto """ ) testdir.makepyfile( """ import pytest 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*", "*Kwargs:*", "*assert {} == {'bar': 4}*", "*Right contains* more item*", "*{'bar': 4}*", "*Use -v to*", ] 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.makeini( """ [pytest] asyncio_mode=auto """ ) 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://pytest-mock.readthedocs.io/en/latest/remarks.html#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://pytest-mock.readthedocs.io/en/latest/remarks.html#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 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 warnings.catch_warnings(record=True) 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.makeini( """ [pytest] asyncio_mode=auto """ ) 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.makeini( """ [pytest] asyncio_mode=auto """ ) 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.makeini( """ [pytest] asyncio_mode=auto """ ) 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.makeini( """ [pytest] asyncio_mode=auto """ ) 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 *") def test_stop_patch(mocker): class UnSpy: def foo(self): return 42 m = mocker.patch.object(UnSpy, "foo", return_value=0) assert UnSpy().foo() == 0 mocker.stop(m) assert UnSpy().foo() == 42 with pytest.raises(ValueError): mocker.stop(m) def test_stop_instance_patch(mocker): class UnSpy: def foo(self): return 42 m = mocker.patch.object(UnSpy, "foo", return_value=0) un_spy = UnSpy() assert un_spy.foo() == 0 mocker.stop(m) assert un_spy.foo() == 42 def test_stop_spy(mocker): class UnSpy: def foo(self): return 42 spy = mocker.spy(UnSpy, "foo") assert UnSpy().foo() == 42 assert spy.call_count == 1 mocker.stop(spy) assert UnSpy().foo() == 42 assert spy.call_count == 1 def test_stop_instance_spy(mocker): class UnSpy: def foo(self): return 42 spy = mocker.spy(UnSpy, "foo") un_spy = UnSpy() assert un_spy.foo() == 42 assert spy.call_count == 1 mocker.stop(spy) assert un_spy.foo() == 42 assert spy.call_count == 1 def test_stop_multiple_patches(mocker: MockerFixture) -> None: """Regression for #420.""" class Class1: @staticmethod def get(): return 1 class Class2: @staticmethod def get(): return 2 def handle_get(): return 3 mocker.patch.object(Class1, "get", handle_get) mocker.patch.object(Class2, "get", handle_get) mocker.stopall() assert Class1.get() == 1 assert Class2.get() == 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1711059141.0 pytest-mock-3.14.0/tox.ini0000644000175100001770000000110114577130305014774 0ustar00runnerdocker[tox] minversion = 3.5.3 envlist = py{38,39,310,311,312}, norewrite, pytest6 [testenv] deps = coverage mock pytest-asyncio pytest6: pytest==6.2.5 commands = coverage run --append --source={envsitepackagesdir}/pytest_mock -m pytest tests --color=yes [testenv:norewrite] commands = pytest tests --assert=plain --color=yes [pytest] addopts = -r a 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:}