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}: RM{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: RM60" 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.
Try It Yourself
13 minWrite a function that calls random.randint or time.time directly. Patch it in a test so the result is fixed.
Deliberately patch the wrong target (e.g. "requests.get" instead of "app.requests") and observe the test fail to mock. Fix the target.
Patch 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.
Mini-Challenge · 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.
Homework
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.