The single most common unittest.mock mistake is patching requests.get and watching the real HTTP request fire anyway, because the code under test did from requests import get and now resolves get in its own module namespace — not in requests. The canonical rule is "patch where it's looked up, not where it's defined," and it falls directly out of how Python binds names: a from-import copies a reference into the importing module at import time, so patching the source module never touches that copy. This guide explains the binding mechanism, shows the two import forms side by side, and gives a procedure for finding the correct patch target every time.
Prerequisites
- Python 3.x with
unittest.mock(patchlives in the standard library; examples target 3.11). - A clear mental model of module namespaces and import binding. The Patching Strategies for Complex Codebases overview frames the broader problem.
- Familiarity with
patchas a decorator/context manager.
Solution
Trace the import form in the module that calls the dependency, then patch the namespace that module reads from.
# svc.py — where the function is DEFINED
def fetch() -> str:
return "REAL network result"
# app.py — the module UNDER TEST
from svc import fetch # <-- copies the reference: app.fetch is now a name
def run() -> str:
return fetch() # resolves `fetch` in app's OWN namespace
# test_app.py
from unittest.mock import patch
import app
def test_patch_where_looked_up():
# CORRECT: patch the binding app actually resolves at call time.
with patch("app.fetch", return_value="FAKE") as m:
assert app.run() == "FAKE" # the mock replaced app.fetch
m.assert_called_once_with()
def test_patch_source_module_fails():
# WRONG: app already copied the reference; svc.fetch is a different binding.
with patch("svc.fetch", return_value="FAKE"):
# app.fetch still points at the original object -> real code runs.
assert app.run() == "REAL network result"
# --- Contrast: attribute-access import makes the source module the target. ---
# app_attr.py
import svc # keeps a live reference to the module object
def run_attr() -> str:
return svc.fetch() # resolves `fetch` on the svc module AT CALL TIME
def test_attribute_access_patches_source():
import app_attr
# Now patching the source works, because the lookup happens on svc.
with patch("svc.fetch", return_value="FAKE"):
assert app_attr.run_attr() == "FAKE"
Why this works
from svc import fetch executes an assignment: it copies the current value of svc.fetch into app's module dictionary as app.fetch. From then on, app.run() looks up fetch in app's globals, never consulting svc again — so patch("svc.fetch", ...) rebinds a name nothing reads. patch works by setting an attribute on the object named by the dotted path, so you must point it at the exact namespace where the code resolves the name: app.fetch for a from-import, and svc.fetch for import svc followed by svc.fetch(), because that form defers the lookup to call time on the live module object.
Edge cases and failure modes
- Re-imports and aliases shift the target.
from svc import fetch as grabcreatesapp.grab; patchapp.grab. Animport svc as swiths.fetch()still resolves on the originalsvcmodule object, so patchsvc.fetch. - Class methods are attributes of the class, not the caller. To replace a method on instances, patch
module.ClassName.method(or usepatch.object(ClassName, "method")); the binding lives on the class regardless of where instances are created. - Patching builtins and
sys.modulesneeds different targeting.open,print, and module-level singletons resolve through builtins or the import system, not a plain copied name — see patching builtins and sys.modules safely. autospec=Truedoes not change the target, only the double's strictness. You still patch where the name is looked up; pairing the correct target with autospec strict mocking catches signature drift once the patch lands. This matters for mocking network and HTTP calls, where the wrong target silently lets real requests through.- Patch ordering with stacked decorators is bottom-up. Multiple
@patchdecorators inject mocks as arguments in reverse order; a wrong assumption here looks like a wrong target. The mock that does not match its expected call is the misordered one, not necessarily the wrong namespace.
Frequently Asked Questions
Why does patching the function's source module not work? A from-import copies the reference into the importing module's namespace at import time. The code under test resolves the name in its own module, so patching the original source module leaves that copied binding untouched and the real function still runs.
What is the patch where it's looked up rule? Patch the name in the namespace where the code under test reads it, not where the object is defined. If module app imports a function from svc with from svc import fn, patch app.fn, because app.fn is the binding the code resolves.
Does import module then module.func avoid the binding problem? Yes. With import svc and a call to svc.fn, the code resolves fn on the svc module object at call time, so patching svc.fn works. The fragile case is from svc import fn, which copies the reference into the caller.
Related guides
- For builtins, environment, and module-level singletons, read patching builtins and sys.modules safely.
- Once the target is right, lock the double's contract with autospec strict mocking.
- If the dependency can be injected instead of patched, injecting fakes vs mocks in constructors avoids target headaches entirely.
- When the patched object is awaited, choose the class via Mock vs MagicMock vs AsyncMock — when to use each.