Desk Clarity

Everyday document & browser help

Mock Patch Has No Effect: Check the Consumer’s Imported Binding

By ON Lab · Sources checked November 8, 2026

patch changes a particular name binding; it does not replace every reference to the original object. If a consumer imported a function with from module import name, patch the consumer’s lookup name. A consumer calling module.name has a different lookup path. Inspect the import style and verify that the mock was actually called.

Reproduce both import styles without real requests

The original public question compared from requests import Session with requests.Session. We used three tiny local modules instead, so an unsuccessful patch could not contact a service.

Create source.py:

def fetch():
    return "real"

Create consumer.py:

from source import fetch
def run():
    return fetch()

Create module_consumer.py:

import source
def run():
    return source.fetch()

Keep the filenames and directory clear; an unrelated module with the same import name would test a different object. The next script belongs in that same disposable directory.

Patch the provider and observe the difference

from unittest.mock import patch
import consumer
import module_consumer
with patch("source.fetch", return_value="fake") as mocked:
    print(consumer.run())
    print(module_consumer.run())
    print(mocked.call_count)

Our Python 3.13.18 fixture printed real, fake and 1. The direct-import consumer retained its own binding. The module-style consumer looked up source.fetch when called and reached the patched binding. One mock call did not mean both consumers used it.

Patch the direct consumer’s actual lookup

from unittest.mock import patch
import consumer
with patch("consumer.fetch", return_value="fake") as mocked:
    assert consumer.run() == "fake"
    mocked.assert_called_once_with()
assert consumer.run() == "real"

This reached the mock, then restored the original binding when the context ended. The context manager makes the lifetime visible. A test result based only on a returned value could be misleading if the real function happened to return the expected value; checking the mock’s call makes that distinction explicit.

“Where used” means the lookup expression

Read the expression the consumer executes. In consumer.run it is fetch from consumer’s module globals. In module_consumer.run it is source.fetch. The original function’s definition location is relevant, but it is not automatically the correct target for every imported alias.

An additional alias changes the lookup name again. A value captured in a default argument, closure or already-created object may introduce another boundary. Do not extend this two-module rule into a promise that changing one module attribute will replace every earlier captured reference.

Check identity and scope when results still differ

  1. Confirm which module file was imported and which import style the consumer uses.
  2. Identify the exact binding reached by its call expression.
  3. Patch that target within an explicit lifetime.
  4. Check mock calls and the result separately.
  5. After the context, confirm restoration in a harmless fixture.

If a real operation could have external effects, first reproduce the namespace behavior with an inert function like this one. Do not use an actual message send, payment or live database call merely to see whether a patch worked.

Keep configuration changes separate from patching

Changing sys.path or reloading a module to make one test pass can alter import identity and side effects. Those are separate operations requiring their own reason. The namespace fixture here needs no third-party package and does not reproduce Requests, pytest or a framework’s dependency injection.

The free unittest.mock documentation and original public answer already explain the rule. This guide adds both import styles, an exact call count and a verified restoration step so that an apparently successful test is not mistaken for proof that its intended binding was replaced.

Method: current Python 3.13 mock documentation, an inspected user question and a three-module Python 3.13.18/macOS fixture. No external request, current Requests behavior or every captured-reference case was tested.

Sources