skip to content

Why must unittest.mock.patch target where a name is looked up, not where it is defined?

level: middleimportance: must knowfreq 72%

answer

  1. Two names, one object
  2. Ask what the caller's namespace holds
  3. from-import copies the reference at import time
  4. Patch the consuming module's binding
  5. patch() is setattr on the named module

basics

~20 s

A from-import copies the referenced object into the importing module's own namespace, so replacing the attribute in the defining module leaves that copy untouched and the patch does nothing. Patch the name in the module under test instead.

solid answer

~40 s

`patch("pkg.helper")` is just `setattr` on the `pkg` module object. If the module under test did `from pkg import helper`, it holds its own global binding to the same function, and rebinding `pkg.helper` never touches it — the real code runs, the mock records nothing, and the test usually still passes, which is what makes this so dangerous. Patch the consuming module's binding: `patch("app.helper")`. If the module instead did `import pkg` and calls `pkg.helper()`, the attribute lookup happens at call time, so `patch("pkg.helper")` does work — at the cost of affecting every user of `pkg` for the duration. When you already hold the module or class in the test, `patch.object(app, "helper")` says the same thing without a fragile string, and asserting on the mock proves the patch actually took effect.

code

python · 17 lines
python
import sys
import types
from unittest.mock import patch

dep = types.ModuleType("dep")
exec("def now():\n    return 'real'\n", dep.__dict__)
sys.modules["dep"] = dep

app = types.ModuleType("app")
exec("from dep import now\n\ndef handler():\n    return now()\n", app.__dict__)
sys.modules["app"] = app

with patch("dep.now", return_value="fake"):
    print(app.handler())

with patch("app.now", return_value="fake"):
    print(app.handler())

go deeper

for a junior

Recall the slogan and one concrete example: if the code says from pkg import helper, patch app.helper, not pkg.helper. Being able to state the rule and apply it to a snippet is enough at this level.

for a middle

Explain the mechanism, not the slogan: from-import is an assignment that binds a second name to the same object, and patch is setattr on one of them. Be ready to say why import pkg plus pkg.helper() behaves differently, and what patch does when the attribute is missing.

for a senior

Show how you detect a silent no-op patch — asserting on the mock, specced doubles, checking blast radius when patching a shared module — and how you steer code toward being patchable, such as reaching through the module rather than from-importing a collaborator.

for a principal

Own the testability convention: whether collaborators are from-imported at all, whether dependencies are injected instead of patched, and how much patching of third-party internals your codebase tolerates. Every dotted target string is a coupling to someone else's module layout.

The rule is usually stated as "patch where it is used, not where it is defined", and the reason is entirely about how Python's name binding works — `unittest.mock` has no special knowledge of imports. ## Two kinds of import, two kinds of binding `import` and `from ... import` do different things to namespaces. - `import helpers` binds one name, `helpers`, to the module object in the importing module's global namespace. Every later `helpers.fetch(...)` is two operations: look up `helpers` in globals, then look up `fetch` as an attribute of that module object — and the second lookup happens at *call* time. - `from helpers import fetch` instead evaluates `helpers.fetch` once, at import time, and binds the resulting function object to the name `fetch` in the *importing* module's globals. There are now two independent bindings pointing at the same function object: `helpers.fetch` and `app.fetch`. ## Why `patch("helpers.fetch")` misses `app.fetch` `patch("helpers.fetch")` does `setattr(helpers, "fetch", mock)`. It rebinds one of those two names. `app.fetch` still points at the original function, because rebinding a name never touches other names that happen to reference the same object. So `app.handler()` calls the real function, the mock records nothing, and the test frequently still passes — the code did its real work, the assertions were about the result rather than the call, and nothing anywhere reports that the patch was a no-op. That silence is what makes this the most-asked question about `patch`: the failure mode is not an exception, it is **a test that is quietly not testing what it claims**. The fix is to patch the binding that the code under test actually reads: `patch("app.fetch")`. The target string is not "the library path of the function", it is "the attribute to replace, spelled as *module path* dot *attribute name*" — and the module path is the module doing the calling. ## What follows from the rule Three corollaries follow. 1. **If the module under test uses `import helpers` and calls `helpers.fetch()`, then `patch("helpers.fetch")` does work**, because the attribute lookup on the module object happens at call time and therefore sees the replacement. That form is also broader than it looks: for the duration of the patch, *every* module in the process that reaches `fetch` through the `helpers` module object sees the mock. Inside a single-threaded test that is usually harmless and sometimes desirable, but it is a real difference in blast radius from patching the one consuming module's name. 2. **If the module under test imports inside the function body**, the import statement runs on every call, fetches the module from `sys.modules`, and rebinds a fresh local name from the current module attribute — so patching the defining module works there too. 3. **A class or object reference behaves the same way.** `from helpers import Client` copies the class object into the consuming module, and `patch("app.Client")` is what replaces it for that module's `Client()` calls. When you already hold the object — the class, an instance, a module you imported in the test — `patch.object(app, "Client")` says the same thing without a string, and has the advantage that a typo is a `NameError` or `AttributeError` at import rather than an argument that only fails when the test runs. ## How to tell whether a patch actually took effect - The strongest routine check is an assertion on the mock: `fake.assert_called_once_with(...)` fails loudly if the real function ran instead. - A weaker but useful habit is to make the mock's return value obviously synthetic, so a test that "passes" while producing real-looking output is suspicious. - When a patch is silently doing nothing, printing the attribute inside the test — or checking `app.fetch` against the mock object — settles it in seconds. ## Where the target string can genuinely fail loudly If the attribute does not exist at all on the named module, `patch` raises `AttributeError` when it starts, unless you pass `create=True`. Reaching for `create=True` to make that error go away is almost always wrong: it converts a real "this name is not there" signal into an invented attribute nobody reads. ## Not a hook, not a bug Finally, note what this rule is *not*. It is not a special import hook, not something `patch` could fix by being cleverer, and not a bug. It is the ordinary consequence of `from x import y` being an assignment. The same rule explains why reassigning a module-level constant in one module does not change another module's from-imported copy, and why the safest style for code you expect to test is to reach through the module (`helpers.fetch()`), which leaves exactly one binding to patch.

  • If the module under test does `import json` and calls json.dumps(), where do you patch?
    `patch("json.dumps")` works, because the attribute is looked up on the `json` module object at call time and therefore sees the replacement. The tradeoff is blast radius: for the duration, every module in the process that reaches `dumps` through the `json` module gets the mock, not just the one under test. In a single-threaded test that is usually fine, but it is a real difference from patching one consumer's own binding.
  • What happens if the target attribute in the patch string does not exist?
    `patch` raises `AttributeError` when it starts, which catches typos in the attribute name. Passing `create=True` suppresses that check and invents the attribute — almost always the wrong response, because it turns a genuine 'this name is not there' signal into a mock nobody reads. Note the check cannot save you from the common failure: a correctly spelled name patched in the wrong module.
  • How do you prove in the test itself that the patch took effect?
    Assert on the double: `fake.assert_called_once_with(...)` fails loudly if the real function ran instead. A test that installs a mock and never touches it would not notice the patch being a no-op. Building the double with a spec helps too, since an unexpected attribute then raises instead of auto-creating a child mock.

A from-import is like photocopying one page out of a reference book: correcting the book afterwards does nothing to the copy already pinned to somebody's wall.

saying these in an interview costs you the question

  • Always patches the function where it is defined
  • Thinks patch rewrites the function object itself everywhere
  • Treats a passing test as proof the patch applied
  • Cannot explain what from-import binds
  • Adds create=True to silence an AttributeError
  • Believes unittest.mock hooks the import system

context