What this usually means
The most common cause is patching the wrong import path. Python's `mock.patch` replaces the object in the namespace where it is *looked up*, not where it is *defined*. If you patch `module_a.Foo` but the code under test imports `Foo` via `from module_b import Foo`, the mock is applied to `module_a` while the code uses `module_b`'s reference. Another subtle variant: you patch a class method on the class object, but the code calls the method via an instance – if the method is looked up on the instance's class at call time, the patch may work, but if it's a built-in or C extension method, patching the class may not propagate to all instance calls. This guide focuses on the wrong-target problem, which is the #1 reason patches silently fail.
The first ten minutes — establish facts before touching code.
- 1Add a print or log statement inside the real function to confirm it's being called despite the patch.
- 2Temporarily raise an exception inside the real function to see if the test hits it.
- 3Check the patch target string – it must match the import path used in the *module under test*, not the definition module.
- 4Run `python -c "from mymodule import MyClass; print(MyClass.my_method)"` to verify the path resolves.
- 5Use `warnings.warn` or `__debug__` in the real function to detect unexpected calls.
- 6Insert `assert False` in the patched function to force a test failure if the real code runs.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchThe `patch()` decorator or context manager arguments – verify the target string.
- searchThe `import` statements in the file that *uses* the mocked object (the system under test).
- searchThe `__init__.py` files that might re-export names and change the lookup path.
- searchThe `sys.modules` in a debugger to see which module object is being patched.
- searchThe test file's import section – sometimes you accidentally import the real module before patching.
- searchThe order of decorators on the test function – lower decorators are applied first (closer to the function).
- searchThe `side_effect` or `return_value` of the mock – if they're not set, the mock may return another mock.
Practical causes, not theory. These are the things you will actually find.
- warningPatching the definition location instead of the lookup location (e.g., `package.lib.func` instead of `module_under_test.func`).
- warningPatching a class method but the code calls it via an instance that was created before the patch took effect.
- warningPatching an attribute that is cached or stored in a local variable before patching.
- warningPatching a built-in function (like `open`, `time.time`) without accounting for the import style (e.g., `from time import time` vs `import time`).
- warningPatching the wrong module due to namespace packages or relative imports.
- warningPatching a method on an instance instead of the class, but the code creates new instances after patching.
- warningUsing `patch.object` with the wrong object reference (e.g., a local import vs the module's attribute).
Concrete fix directions. Pick the one that matches your root cause.
- buildChange the patch target to match the import path in the module under test: `patch('module_under_test.func')` instead of `patch('library.func')`.
- buildIf the code uses `from X import Y`, patch `X.Y` where `X` is the module that *imports* `Y` (the consumer), not the module that defines `Y`.
- buildFor instance methods, patch the class before any instances are created, or use `patch.object(MyClass, 'method')`.
- buildFor built-ins like `open`, ensure you patch the name in the module that uses it: `patch('myapp.utils.open', ...)`.
- buildUse `patch('module.ClassName.method')` only if the code calls `ClassName.method()` directly, not via instance.
- buildIf the code caches a reference in a module-level variable, patch that variable after it's set, or restructure the code.
- buildUse `wraps` parameter to ensure the real function is called when you want, but you can still track calls.
A fix you cannot prove is a guess. Close the loop.
- verifiedAfter applying the fix, assert that the mock's `call_count` is > 0 after the test runs.
- verifiedTemporarily add `assert False` in the real function – the test should now fail because the mock replaced it.
- verifiedPrint `id(real_func)` and `id(mocked_func)` before and after patching to confirm they differ.
- verifiedUse `patch` as a context manager and check `sys.modules['target_module'].func is mock_obj`.
- verifiedSet a breakpoint inside the real function – if you hit it, the patch is not effective.
- verifiedRun the test with `-s` flag to see print output from the real function if it's called.
Things that make this bug worse or harder to find.
- warningDon't patch the same object in multiple places – order matters and one may override the other.
- warningDon't patch a method on an instance that is created in the test setup before the patch is applied.
- warningDon't forget that `patch` decorators stack from bottom to top – the one closest to the function runs first (is the outermost).
- warningDon't assume that patching the class method will affect all instances – it does, but only if the method is resolved via the class.
- warningDon't patch a name that is imported using `from ... import ...` in the test file itself – that binds a local reference that patch won't affect.
- warningDon't forget to stop the patcher if using `start()`/`stop()` manually – use a try/finally block.
- warningDon't use `patch('builtins.open')` if the code uses `from builtins import open` – patch the consumer's namespace.
The Silent Email That Escaped the Mock
Timeline
- 09:15Deploy a new user registration endpoint with email notification via Celery task.
- 10:00Write unit test for the registration endpoint; mock the Celery task using `patch('tasks.send_welcome_email')`.
- 10:30Test passes. All assertions green. Deploy to staging.
- 11:00QA reports that staging environment sent real welcome emails to test accounts.
- 11:15I check the test – mock's call_count is 0. Real SendGrid API was hit.
- 11:20Realize the target: I patched `tasks.send_welcome_email`, but the registration code does `from workers.email import send_welcome_email`.
- 11:25Fix: change patch target to `app.routes.send_welcome_email` (the consumer module).
- 11:30Test now fails because mock is not configured with return value – but at least it's called.
- 11:35Add proper mock configuration. Test passes. Repeat for other modules.
I had just shipped a feature: when a user registers, a background task sends a welcome email via SendGrid. The unit test used `@patch('tasks.send_welcome_email')` to mock the Celery task. The test passed, but when QA ran the staging deployment, real emails arrived in their inboxes. I was sure the mock was in place – the test had a green checkmark.
I added a print inside the real `send_welcome_email` function. The test printed the line, confirming the real function was called. Then I checked the mock's `call_count` – 0. That's when I remembered the golden rule: patch where the name is looked up, not where it's defined. The registration endpoint in `app/routes.py` imported the task with `from workers.email import send_welcome_email`. My patch targeted `tasks.send_welcome_email`, but the lookup happened in `app.routes`.
I changed the patch target to `'app.routes.send_welcome_email'`. Now the mock was hit. The test initially failed because I hadn't set a return value, but that was a quick fix. I also scanned the codebase for similar patterns and fixed two other tests that had the same bug. The key lesson: always trace the import chain in the file under test, not the definition file.
Root cause
Patching the definition module (`tasks.send_welcome_email`) instead of the consumer module (`app.routes.send_welcome_email`).
The fix
Changed `@patch('tasks.send_welcome_email')` to `@patch('app.routes.send_welcome_email')` and configured the mock properly.
The lesson
Always patch the name in the namespace where the code under test looks it up. Use `print(f'{__name__}.{func.__name__}')` to verify the target path.
Python's `import` statement creates a binding in the local namespace. When you write `from module import name`, the name is a reference to the object in `module` at the time of import. Patching `module.name` does modify the object in `module`, but the local binding in the consumer module still holds the original object. Therefore, the patch must target `consumer_module.name`, not `definition_module.name`.
Conversely, if the consumer uses `import module` and then `module.name`, patching `module.name` works because the consumer accesses the attribute via the module object. So the rule: if the consumer does `from X import Y`, patch `X.Y` is wrong – patch the consumer's module (the one that contains the import statement). If the consumer does `import X; X.Y`, patching `X.Y` works.
Another subtle failure: patching a method on a class, but the code under test calls the method on an instance that was created before the patch. When you call `instance.method()`, Python looks up the method on the class at call time (unless it's a data descriptor). So patching the class should affect all instances, even ones created before the patch. However, if the method is a built-in or implemented in C (e.g., `dict.get`), the lookup may be cached. Also, if the code stores a reference to the bound method (e.g., `self.method_ref = self.method`), patching the class won't update that stored reference.
To debug, check if the method is retrieved once and stored, or if it's looked up each time. Use `inspect.ismethod` on the stored reference. If it's a bound method object, it's a snapshot; the patch won't affect it. The fix is to patch at the point where the reference is stored, or refactor the code to avoid caching method references.
Built-in functions like `open`, `time.time`, `datetime.now` are often imported via `from time import time`. Patching `time.time` won't affect a consumer that did `from time import time` – you must patch `consumer_module.time`. However, sometimes the built-in is accessed via `import builtins` and then `builtins.open`. In that case, patching `builtins.open` works. The safest approach is to patch the name in the module that uses it.
For C extensions, the attribute lookup may behave differently. For example, `os.path.exists` is a function from a C module. Patching `os.path.exists` works if the consumer does `import os.path` and uses `os.path.exists`. If the consumer does `from os.path import exists`, patch `consumer_module.exists`. Some C functions are not patchable at all because they are not Python objects – but that's rare. In those cases, consider using a wrapper function that you can mock.
When a patch silently fails, the test passes but the real code runs. To detect this, add a temporary probe: insert `raise RuntimeError('PATCH FAILED')` as the first line of the real function. If the test passes, the patch was effective. If it fails, the real function ran. This is the fastest way to confirm the patch target is wrong.
Another technique: use `unittest.mock.patch` as a context manager and assign the mock to a variable. After the test, check `mock_obj.called` and `mock_obj.call_count`. If they're zero, the patch didn't intercept. Also, you can set `side_effect` to a lambda that prints a traceback to see where the call came from.
In threaded or async environments, patches applied in one thread don't affect other threads because each thread has its own module namespace (though the module objects are shared, the attribute lookup is atomic but the patch may race). For `asyncio`, the event loop runs in a single thread, so patches work, but be careful with `asyncio.run()` which creates a new event loop and may reimport modules. Always apply patches inside the async test function or use `asyncio`-specific test utilities like `pytest-asyncio`.
For threading, the safest approach is to mock at a higher level (e.g., mock the entire queue or the function that submits tasks) rather than the low-level function. Alternatively, ensure patches are applied before threads are started. Use `threading.Barrier` to synchronize.
Frequently asked questions
Why does patching a class method sometimes work and sometimes not?
Patching a class method works if the code calls the method via the class or an instance, because Python looks up the method on the class at call time. However, if the code stores a bound method reference (e.g., `self.my_method = self.method`), the patch won't affect that stored reference. Also, for built-in methods or C extensions, the lookup might be cached or immutable.
How do I find the correct target for patch?
Look at the import statement in the file that contains the code under test. If it says `from foo import bar`, the target is `tested_module.bar`. If it says `import foo; foo.bar`, the target is `foo.bar`. You can also run `python -c "from tested_module import bar; print(bar.__module__)"` to see the definition module, but remember the target is the consumer's namespace.
Can I patch an object that is used in a list comprehension or generator expression?
Yes, but the patch must be applied before the expression is evaluated. List comprehensions and generator expressions execute in a new scope, but they look up names from the enclosing scope. As long as the patch is applied to the enclosing module's name, it will affect the expression. However, if the expression is defined in a function and the patch is applied after the function is defined but before it's called, it works.
What's the difference between `patch` and `patch.object`?
`patch` takes a string target in dot notation (e.g., `module.ClassName.method`). `patch.object` takes an object and a string attribute name (e.g., `patch.object(MyClass, 'my_method')`). They are equivalent when the target is a module attribute. `patch.object` is useful when you already have a reference to the object. Both suffer from the same wrong-target issue – the attribute must be looked up on the object you provide.
How do I mock a global variable that my module uses?
Treat the global variable like a function: patch the name in the module that uses it. For example, if `config.py` defines `API_KEY = 'real'` and `app.py` does `from config import API_KEY`, patch `app.API_KEY`. If `app.py` does `import config; config.API_KEY`, patch `config.API_KEY`. Note that if the value is a mutable object like a list, you may need to patch the list itself, not just reassign the name.