Learning Goals
3 min- Replace a function/object with
@patchfor one test. - Apply the "patch where it's used" rule.
- Use patch as a decorator, context manager, or fixture.
- Use
autospec=Trueto catch bad mock usage.
Warm-Up · When You Can't Inject
5 min# app.py import requests def get_user_count(): resp = requests.get("https://api.example.com/users") # hard-wired call return len(resp.json())
There's no dependency to inject — requests.get is called directly. @patch lets you replace it during the test without touching the code.
@patch("target") swaps an object for a MagicMock during a test and restores it afterwards. The famous gotcha: you patch the name in the module that uses it (app.requests), not where it's originally defined (requests).
New Concept · @patch & the Golden Rule
14 minAs a decorator — a mock is passed in
from unittest.mock import patch @patch("app.requests") # patch requests AS USED IN app.py def test_user_count(mock_requests): mock_requests.get.return_value.json.return_value = ["a", "b", "c"] assert get_user_count() == 3 mock_requests.get.assert_called_once()
The decorator injects the mock as the first extra argument. Stacked decorators inject bottom-up (closest decorator = first argument).
The golden rule: patch where it's USED
# app.py does: import requests ... requests.get(...)
# ✓ patch the name in app's namespace:
@patch("app.requests")
# ✗ this does NOT work — app already imported its own reference:
@patch("requests.get") # (sometimes works, often a trap)If app.py did from requests import get, you'd patch "app.get". Always patch the name as the code under test looks it up.
As a context manager — scoped to a block
def test_user_count_ctx(): with patch("app.requests") as mock_req: mock_req.get.return_value.json.return_value = [1, 2] assert get_user_count() == 2
patch.object — patch one attribute
with patch.object(MyClass, "save", return_value=True) as mock_save: obj.do_work() mock_save.assert_called()
autospec — catch wrong usage
# without autospec, a typo'd call silently "works": @patch("app.requests") def test(m): m.gett() # typo — a plain mock happily returns another mock! # with autospec, the mock matches the real signature: @patch("app.requests", autospec=True) def test(m): m.gett() # raises AttributeError — caught!
autospec=True makes the mock enforce the real object's API, catching mistakes a loose mock would hide. Use it when patching something with a known interface.
Worked Example · Patch the Clock and the Network
12 min# report.py import requests from datetime import datetime def daily_report(): today = datetime.now().strftime("%Y-%m-%d") data = requests.get("https://api.example.com/sales").json() total = sum(data) return f"{today}: ${total}"
# test_report.py from unittest.mock import patch from report import daily_report @patch("report.requests") @patch("report.datetime") def test_daily_report(mock_datetime, mock_requests): # NOTE arg order: bottom decorator (datetime) is the FIRST arg mock_datetime.now.return_value.strftime.return_value = "2026-05-28" mock_requests.get.return_value.json.return_value = [10, 20, 30] assert daily_report() == "2026-05-28: $60" mock_requests.get.assert_called_once()
$ pytest test_report.py -v test_daily_report PASSED 1 passed ← deterministic date AND no network call
Read the diff
Two patches: the clock (so "today" is always 2026-05-28) and the network (so sales are always [10,20,30]). Both patched as used in report (report.datetime, report.requests). Watch the decorator-argument order — it's bottom-up, the #1 confusion with stacked @patch. The test is now fully deterministic and offline.
Basic
5 minWrite a function that calls random.randint or time.time directly. Patch it in a test so the result is fixed.
Challenge 1
4 minDeliberately patch the wrong target (e.g. "requests.get" instead of "app.requests") and observe the test fail to mock. Fix the target.
Challenge 2
4 minPatch an object two ways — plain and with autospec=True. Call a misspelled method on each. Show autospec catches it while the plain mock hides it.
Challenge 3 · Patch as a Fixture
8 minWhen several tests patch the same thing, move it into a fixture. Use pytest's monkeypatch OR a yield fixture wrapping patch. Write a fixture that patches the network for a whole module of tests.
Show one possible solution
import pytest from unittest.mock import patch @pytest.fixture def fake_api(): with patch("app.requests") as mock_req: mock_req.get.return_value.json.return_value = {"ok": True} yield mock_req # tests get the mock; patch undone after def test_one(fake_api): assert call_api()["ok"] is True fake_api.get.assert_called_once() def test_two(fake_api): # fresh patch each test fake_api.get.return_value.json.return_value = {"ok": False} assert call_api()["ok"] is False
Non-negotiables: the fixture yields the mock and the patch is automatically undone after each test (the with block closes on teardown).
Recap
3 min@patch("module.name") swaps an object for a MagicMock during a test and restores it after. Patch where the name is looked up, not where it's defined. Stacked decorators inject bottom-up. Use it as decorator, context manager, or inside a fixture. Add autospec=True to catch wrong usage. Next: putting it together to mock a database.
Vocabulary Card
- @patch
- Temporarily replace an object with a mock for the test's duration.
- patch where used
- Target the name in the module under test, not the original module.
- patch.object
- Patch a single attribute/method of a specific object or class.
- autospec
- Make the mock match the real object's signature, catching bad calls.
Extra Mission
4 minTake a function that calls requests, the clock, or random directly (no injection). Write tests that @patch the dependency — at least one with a fixed return, one with a side_effect error, and one verifying the call. Patch the correct "where it's used" target. Bonus: a patch fixture.
Model on test_report.py. Double-check the patch target matches the import style in the code under test.