pytest-mock: patch, autospec, spies and call assertions: working versions (tested 2026-10-18)
Core pytest-mock patterns on a small notification service: loose mocker.patch, autospec signature checks, mocker.spy over real methods, patch.object stubs and assert_called_once_with.
passed5 passed · 0 failed2026-10-18 03:57 UTC- Status
- passed
- Last passed
- 2026-10-18 03:57 UTC
- Tests
- 5 / 5
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 39095370682
The code
Exactly the files that passed. Your agent can pull them with get_recipe("pytest-mock-spy-autospec") (install), or see them on GitHub.
pytest-mock==3.17.0 pytest==9.1.1
import smtplib
from dataclasses import dataclass, field
@dataclass
class Notifier:
sent: list = field(default_factory=list)
def send(self, to: str, body: str) -> int:
if not to:
raise ValueError("missing recipient")
self.sent.append((to, body))
return len(self.sent)
def broadcast(notifier: Notifier, recipients: list, body: str) -> int:
total = 0
for to in recipients:
total += notifier.send(to, body)
return total
def send_via_smtp(host: str, port: int, to: str, body: str) -> bool:
with smtplib.SMTP(host, port, timeout=5) as smtp:
smtp.sendmail("[email protected]", [to], body)
return True
import pytest
from service import Notifier, broadcast, send_via_smtp
def test_plain_patch_accepts_anything(mocker):
n = Notifier()
mock_send = mocker.patch.object(n, "send")
n.send("[email protected]", "hi", "extra-nonsense", typo=True)
mock_send.assert_called_once_with("[email protected]", "hi", "extra-nonsense", typo=True)
def test_autospec_enforces_signature(mocker):
n = Notifier()
mock_send = mocker.patch.object(n, "send", autospec=True)
with pytest.raises(TypeError):
n.send("[email protected]")
mock_send.assert_not_called()
def test_spy_runs_real_method(mocker):
n = Notifier()
spy = mocker.spy(n, "send")
assert broadcast(n, ["[email protected]", "[email protected]"], "hi") == 3
assert n.sent == [("[email protected]", "hi"), ("[email protected]", "hi")]
assert spy.call_count == 2
assert spy.spy_return == 2
spy.assert_called_with("[email protected]", "hi")
def test_patch_object_stubs_return_and_args(mocker):
n = Notifier()
mock_send = mocker.patch.object(n, "send", return_value=42)
assert broadcast(n, ["[email protected]"], "hi") == 42
mock_send.assert_called_once_with("[email protected]", "hi")
assert n.sent == []
def test_patch_context_manager_and_call_args(mocker):
smtp_cls = mocker.patch("service.smtplib.SMTP", autospec=True)
smtp = smtp_cls.return_value.__enter__.return_value
assert send_via_smtp("mail.local", 2525, "[email protected]", "hi")
smtp_cls.assert_called_once_with("mail.local", 2525, timeout=5)
smtp.sendmail.assert_called_once_with("[email protected]", ["[email protected]"], "hi")
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
BeautifulSoup 4 + soupsieve CSS selector scraping
Scrape fixture HTML with BeautifulSoup and soupsieve CSS selectors to pull table cells, links and data attributes, plus the html.parser tbody gotcha.
Capture loguru logs in pytest caplog via a propagate sink and structured extra
Forward loguru records into stdlib logging so pytest caplog sees them, keep bound context as structured extra, and test it offline without a real log backend.
FastAPI dependency_overrides tested offline with httpx ASGITransport
Override FastAPI dependencies (including nested ones) and drive the app in-process with httpx.ASGITransport, no network and no running server.
FastAPI metrics with prometheus_client scraped as text
Instrument FastAPI with prometheus_client using a per-app registry, label by route template to bound cardinality, and scrape the text exposition format offline through httpx.ASGITransport.