# pytest-mock and the mocker fixture: patching, spy and stub

> pytest-mock wraps an existing patching API in a single pytest fixture so every patch is undone after the test, and it adds spy and stub beside patch. Its declared footprint is one dependency, pytest>=6.2.5, on Python 3.10 and later.

**pytest-dev/pytest-mock** — Thin-wrapper around the mock package for easier use with pytest

- Repository: https://github.com/pytest-dev/pytest-mock
- Website: https://pytest-mock.readthedocs.io/en/latest/
- Stars: 2,040 · Forks: 186
- Language: Python
- License: MIT
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/pytest-dev-pytest-mock

## Why the mocker fixture replaces patch context managers in pytest

pytest ships the whole unittest.mock library through the standard library, yet every patch still needs a lifetime: either a with block that closes inside the test body, or a fixture that hands the mock object to the test and undoes it afterwards. pytest-mock moves that lifetime into one fixture named `mocker`, so the dependency shows up in the test signature instead of in a nested block. The README describes it as a fixture which is a thin-wrapper around the patching API, and that framing matters more than it first looks. Nothing here reimplements patching. The plugin binds an existing patching surface to pytest fixture semantics and makes the undo automatic at the end of the test, so a test body no longer has to remember to close anything. Call comparison is handed over as well: the plugin uses pytest introspection when comparing calls, which puts a failed assertion through pytest's own rendering rather than the raw message a bare mock produces. For a suite that mixes patch.object, patch.dict and module level patching, the return on the wrapper is uniform teardown and one consistent assertion style, not new patching capability.

## The pytest11 entry point loads the plugin into every session

Registration happens through the standard pytest plugin group declared in pyproject.toml, with the module target spelled the same way as the distribution name:

```toml
[project.entry-points.pytest11]
pytest_mock = "pytest_mock"
```

Because discovery runs through that entry point rather than through a conftest hook, the fixture is a property of the environment, not of one repository. Installing pytest-mock into a virtualenv changes every test run performed in that environment, including runs of projects that never reference it. Neither the README nor pyproject.toml documents an opt-out flag or an ini setting that disables the plugin, so no switch is available from these files. Type information travels with the code: the package data section lists `py.typed` for `pytest_mock`, which means editors and type checkers can resolve the fixture signature rather than falling back to an unknown attribute.

## spy and stub ride on the same fixture as patch

Three utilities sit on the mocker fixture. patch is the one with an example in the README; the other two are named `spy` and `stub` in a single sentence, with the note that they are utilities offered besides automatic undo. No code sample accompanies them, and no parameter list, return value or recording behaviour is given for either. The README sends the reader to the hosted documentation for full detail, which is where those semantics live. The practical consequence is clear: a team moving from manual patch.object calls to mocker.patch has a one to one mapping to copy, while for spy and stub there is nothing in the repository files to copy at all, and anyone writing a shared fixture wrapper around mocker has to open the documentation first to learn which attributes exist.

## Patching os.remove in a test and asserting the call

One compact, complete example in the README shows the pattern the fixture exists for:

```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')
```

The patch targets the name inside the os module, so the call that UnixFS.rm makes into os.remove is intercepted without touching UnixFS at all. assert_called_once_with then checks both the number of calls and the exact argument, so a test that passes on a wrong argument still fails. Note what the assertion is not doing: it does not set a return value, because os.remove returns nothing and the wrapper code under test ignores it. That is why this shape works for void helpers, where a return value assertion would add nothing. After the test the fixture undoes the patch, which is the whole reason the mock is reached through a fixture rather than through a local context manager.

## Install surface, Python floor and the version file setuptools-scm writes

No install command appears in the README. Its python badge links to the PyPI listing for pytest-mock and its anaconda badge links to conda-forge, so distribution happens through the package index rather than a documented pip invocation. Install constraints are authoritative in pyproject.toml:

```toml
requires-python = ">=3.10"
dependencies = [
    "pytest>=6.2.5",
]
```

Version numbers are not written by hand. The project table sets dynamic = ["version"] and the setuptools_scm section writes the computed value to `src/pytest_mock/_version.py` at build time, which is why the source tree has no checked-in version constant to read. Contributors add their tooling through optional dependencies:

```toml
[project.optional-dependencies]
dev = [
    "pre-commit",
    "pytest-asyncio",
    "tox",
]
```

That trio points at the three ways this project is checked: tox for environments, `.pre-commit-config.yaml` for hooks, and pytest-asyncio for async coverage, with `mypy.ini` at the top level for type checking. The plugin supports Python 3.10 through 3.15 and pytest 6.2.5 or later, so a suite on an older interpreter cannot install it at all.

## The README names a mock dependency the metadata no longer declares

Here is a contradiction worth knowing about before you read the rest of the docs. The repository description and the opening paragraph of the README both frame the plugin as a thin-wrapper around the patching API provided by the mock package, with a link to the mock listing on PyPI. pyproject.toml declares exactly one runtime dependency and it is not that package; the sole entry is pytest itself at version 6.2.5 or later. So the mock name persists in prose while the declared install graph stays at one package. Two things follow. First, nobody needs to install the mock package alongside pytest-mock, and a reader who goes looking for that dependency will find nothing declared. Second, the wrapper targets a patching surface that ships with Python itself, which is why the plugin can stay a thin layer over stdlib behaviour rather than carrying a second mocking library into every environment.

## Badges point at master while the default branch is main

Several badges in the README badge row still target the old branch name. The coverage badge queries ?branch=master and the pre-commit badge is fetched from results.pre-commit.ci for the master path, while the repository default branch is main. Copying that badge block into a fork or an internal package index gives you links that resolve to nothing. At the top level, .git-blame-ignore-revs hides large mechanical commits from git blame, which is the tell that the formatting history was rewritten at some point:

```
.git-blame-ignore-revs
.pre-commit-config.yaml
CHANGELOG.rst
SECURITY.md
mypy.ini
pyproject.toml
src/
tests/
tox.ini
```

When reading blame output on this project, blame ignores those revisions, so a line attributed to a recent commit may predate the visible change by a long stretch of reformatting.

## What the repository files leave to the hosted documentation

Short by design, the README carries one example, two utility names, a row of badges and a pointer onwards. That means no configuration key for pytest-mock appears anywhere in the README or in pyproject.toml, and no ini option, no fixture redefinition, no timeout and no alternative mocking backend is documented in these files. Someone expecting a plugin-wide settings block will find an absence rather than a table. The python classifiers enumerate 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15 individually, while requires-python states only the floor at 3.10, so the two places that answer the support question do it at different granularity. The topic list covers hacktoberfest, mock and pytest, and the project table carries a Development Status :: 5 - Production/Stable classifier. The last push was on 2026-10-06 and the code ships under the MIT licence, so the source is readable and the import surface stays one package deep.

## Conclusion

Adopt pytest-mock when your suite already runs on pytest and you want one fixture to own patch teardown across many tests; the mechanism is small enough to read in a sitting. Do not adopt it to learn patching, since the wrapper adds no patching capability of its own, and for spy and stub the README names them without showing behaviour. Before rolling it out, open the hosted documentation, because the repository files here carry no configuration keys, then confirm the plugin is installed in every environment that runs the suite.

## FAQ

### Do I need to install the mock package to use pytest-mock?

No. pyproject.toml lists a single runtime dependency, pytest>=6.2.5, and the mock package is not among the declared dependencies, even though the README and the repository description both describe the fixture as a thin-wrapper around the patching API provided by mock.

### Which Python versions does pytest-mock support?

pyproject.toml sets requires-python to >=3.10, and the classifiers enumerate 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15.

### What is the difference between mocker, spy and stub?

The README names mocker.patch as the fixture entry point and lists spy and stub as additional utilities, but it gives no code or behaviour for either of the two. Those semantics are documented on the hosted site rather than in the repository files.

### How is the version number of pytest-mock determined?

The project table sets dynamic = ["version"] and the setuptools_scm section writes the computed value to src/pytest_mock/_version.py at build time, so the version is derived from the repository rather than stored in the source tree.

### Do I need to add a conftest or plugin flag to use pytest-mock?

No. The plugin registers itself through the pytest11 entry point declared in pyproject.toml, so the mocker fixture is available once the package is installed in the environment. No configuration key for disabling it appears in the README or in pyproject.toml.

## Sources

- [License: MIT](https://github.com/pytest-dev/pytest-mock/blob/main/LICENSE)
- [Project website](https://pytest-mock.readthedocs.io/en/latest/)
- [pytest-dev/pytest-mock on GitHub](https://github.com/pytest-dev/pytest-mock)
- [README](https://github.com/pytest-dev/pytest-mock/blob/main/README.md)
- [Releases](https://github.com/pytest-dev/pytest-mock/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pytest-dev-pytest-mock
