pytest-mock review
pytest-mock 3.15.1 adds a `mocker` fixture to pytest and routes its patching behavior through Python's `unittest.mock`. Patches created through the fixture are stopped at fixture teardown, and pytest supplies its assertion introspection when mock-call comparisons fail. The plugin also provides spies, permissive callback stubs, async stubs, scope-specific fixtures, and helpers for stopping or resetting mocks. Version 3.15.1 changed iterator spying: callers must pass `duplicate_iterators=True` before reading the copied iterator from `spy_return_iter`, which fixes an `_tee` object error. It requires Python 3.9 or newer and pytest 6.2.5 or newer.
pytest-mock 3.15.1 installed in 0.3 seconds, used 8 MB across 6 packages, imported in 0.77 seconds, and had 0 audit findings in our sandbox. Add it when a pytest suite already relies on `unittest.mock`; keep `monkeypatch` for simple state changes and fix import boundaries before blaming the fixture.
We installed it
| Install | ✓ · 0.3s | 6 packages on disk · 8 MB |
| Import | ✓ | import pytest_mock in 0.77s · pure Python · py.typed · requires Python >=3.9 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does pytest-mock install cleanly?
Yes. In a fresh container with an empty cache, pip install pytest-mock finished in 0.3s, leaving 6 packages and 8 MB on disk. pip-audit reported no known vulnerabilities.
What does pytest-mock need to run?
Python >=3.9, and nothing compiled: it is pure Python. In our run import pytest_mock succeeded in 0.77s, and the package ships py.typed for type checkers.
pytest-mock or pytest: which should you use?
pytest: Use its built-in monkeypatch fixture for temporary environment, mapping, path, and attribute changes. pytest-mock 3.15.1 installed in 0.3 seconds, used 8 MB across 6 packages, imported in 0.77 seconds, and had 0 audit findings in our sandbox.
When should you not use pytest-mock?
The test only changes environment variables, mapping entries, attributes, or the current directory. Pytest's built-in monkeypatch fixture handles those state changes without another plugin.
Use it if
- A pytest suite already uses `unittest.mock`, but patch start and cleanup code is repeated across tests.
- Call assertions should use pytest's readable diffs while retaining `Mock`, `MagicMock`, `AsyncMock`, `call`, `ANY`, and autospec behavior.
- A test needs to observe a real function's calls, returned values, or raised exception without replacing the function.
- Temporary replacements should end reliably after a function, class, module, package, or session fixture scope.
- The test only changes environment variables, mapping entries, attributes, or the current directory. Pytest's built-in `monkeypatch` fixture handles those state changes without another plugin.
- Developers are patching the module that defines a name instead of the module that reads it. `mocker.patch` follows normal Python lookup rules and cannot correct a target chosen at the wrong import boundary.
- Most assertions describe private call sequences between internal objects. The fixture makes interaction tests shorter, but those tests can still break after a harmless refactor that preserves output.
- The supported runtime includes Python 3.8. pytest-mock 3.15 dropped that interpreter and the current package metadata requires Python 3.9 or newer.
- You want a separate mocking model with expectation syntax or automatic fake generation. The project describes `mocker` as a thin wrapper and deliberately keeps `unittest.mock` semantics.
Setup reality
We installed pytest-mock 3.15.1 in a fresh Python 3.12 Bookworm sandbox. The run took 0.3 seconds, produced 6 installed packages using 8 MB, and showed 4 direct dependencies in our measurement. pip-audit found 0 known vulnerabilities. The distribution is pure Python, carries py.typed, uses the MIT license, and requires Python 3.9 or newer. import pytest_mock worked in 0.77 seconds, with no compiler or operating-system package needed.
There are no credentials or mandatory config files. Once installed, pytest discovers the plugin and injects mocker into a test that asks for it. Import MockerFixture only when annotating the fixture. The default scope ends after one test; class_mocker, module_mocker, package_mocker, and session_mocker keep replacements alive longer. Wider scope can leak assumed call history between tests unless the mock is reset deliberately.
Target the reference used by the code under test. If checkout.py executed from payments import charge, patch checkout.charge; changing payments.charge leaves the imported reference untouched. autospec=True checks the real callable's signature and attributes. create=True does the opposite for a missing attribute and can let a misspelled target pass, so use it only for an API that genuinely appears at runtime.
A spy executes the original function, including database writes, network calls, time reads, and iterator consumption. In 3.15.1, spy_return_iter is populated only when duplicate_iterators=True; duplicating a long iterator also retains its values. mocker.resetall() clears recorded calls on managed mocks, while mocker.stopall() removes active patches. Normal fixture teardown already stops them, so early stop calls are mainly for tests that need the real object again before the test ends.
Patterns
Patch the lookup site patch-imported-function
def test_receipt_is_sent(mocker):
send = mocker.patch('shop.checkout.send_receipt')
checkout(order_id=42)
send.assert_called_once_with(42)Patch `shop.checkout.send_receipt` when that module imported the reference. Patching only the original definition may leave the code under test unchanged.
Provide one replacement result return-fixed-value
def test_converts_currency(mocker):
rate = mocker.patch('pricing.fetch_rate', return_value=1.25)
assert convert(8) == 10
rate.assert_called_once_with('USD', 'EUR')`return_value` applies to every call. Use `side_effect` when calls must raise or produce a sequence instead.
Make a dependency fail raise-dependency-error
import pytest
def test_transport_timeout(mocker):
mocker.patch('client.transport.send', side_effect=TimeoutError('late'))
with pytest.raises(TimeoutError, match='late'):
client.fetch()An exception assigned to `side_effect` is raised when the mock is called. The fixture still removes the patch after the test fails or passes.
Patch with autospec enforce-call-signature
def test_charge_arguments(mocker):
charge = mocker.patch('billing.charge', autospec=True)
create_invoice(customer_id=42, cents=500)
charge.assert_called_once_with(42, amount=500)`autospec=True` rejects calls and members absent from the real target. Objects that create attributes dynamically may need a smaller explicit spec.
Replace one object method patch-object-attribute
def test_clock_value(mocker):
clock = SystemClock()
now = mocker.patch.object(clock, 'now', return_value=100)
assert clock.now() == 100
now.assert_called_once_with()`patch.object` changes this instance for the fixture lifetime. Patch the class instead when every new instance should receive the replacement.
Spy without replacing behavior observe-real-call
def test_total_rounds_each_value(mocker):
spy = mocker.spy(money, 'round_amount')
assert money.total([1.234]) == 1.23
spy.assert_called_once_with(1.234)
assert spy.spy_return == 1.23The spied function still runs in 3.15.1. Any I/O or mutation performed by `round_amount` remains part of the test.
Duplicate a returned iterator copy-spied-iterator
def test_row_iterator(mocker):
spy = mocker.spy(repo, 'iter_rows', duplicate_iterators=True)
assert list(repo.iter_rows()) == [1, 2]
assert list(spy.spy_return_iter) == [1, 2]Version 3.15.1 requires `duplicate_iterators=True` before `spy_return_iter` is available. The duplicate can retain every yielded object in memory.
Create an argument-agnostic callback accept-callback
def test_completion_callback(mocker):
on_complete = mocker.stub(name='on_complete')
run_job(on_complete)
on_complete.assert_called_once_with(status='ok')`mocker.stub()` accepts any arguments. Prefer a spec when a wrong callback signature should fail the test immediately.
Create an async callback stub accept-async-callback
import pytest
@pytest.mark.asyncio
async def test_async_callback(mocker):
notify = mocker.async_stub(name='notify')
await run_job(notify)
notify.assert_awaited_once_with(status='ok')`async_stub` can be awaited with any arguments. `assert_awaited_once_with` proves the coroutine was awaited, not merely created.
Patch an async dependency replace-async-function
import pytest
@pytest.mark.asyncio
async def test_user_lookup(mocker):
lookup = mocker.patch('service.lookup', new_callable=mocker.AsyncMock)
lookup.return_value = {'id': 42}
assert await service.load(42) == {'id': 42}
lookup.assert_awaited_once_with(42)Use the `assert_awaited` family for `AsyncMock`. A normal call assertion alone does not prove that application code awaited it.
Stop a replacement before teardown restore-one-patch
def test_real_clock_after_setup(mocker):
patched = mocker.patch('clock.now', return_value=100)
assert clock.now() == 100
mocker.stop(patched)
assert clock.now() != 100`mocker.stop()` restores one patched target early. Other replacements remain active until they are stopped or the fixture ends.
Clear calls between phases reset-recorded-calls
def test_two_phases(mocker):
send = mocker.patch('worker.send')
worker.prepare()
mocker.resetall()
worker.execute()
send.assert_called_once_with('execute')`mocker.resetall()` clears call history on managed mocks but leaves patches active. `stopall()` restores the real targets instead.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pytest | PyPI | Use its built-in `monkeypatch` fixture for temporary environment, mapping, path, and attribute changes. |
| mock | PyPI | Use the backport only when an older Python runtime lacks the `unittest.mock` behavior the suite needs. |
| flexmock | PyPI | Choose it when the team prefers expectation-oriented calls and ordered interaction declarations. |
More testing guides
pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

