Mocking Where It’s Imported (Not Defined)
If you’ve ever written tests in Python using pytest
and unittest.mock
, you’ve probably seen this
confusing bit of advice:
“You have to mock where the function is imported, not where it’s defined.”
At first glance, this seems counterintuitive — why should you patch the same function name in multiple places when it’s clearly defined in one module? The answer has everything to do with how Python imports work.
The Import Mechanism
When you write this:
# foo.py
def get_data():
return "real data"
# And in another module there is this
# bar.py
from foo import get_data
def process():
return f"processed {get_data()}"
Python copies a reference to foo.get_data
into the namespace of bar
.
So bar.get_data
is not dynamically looked up each time — it’s bound once when bar
is imported.
That means if you want to replace get_data()
in your tests, mocking foo.get_data
won’t help,
because bar already has its own local reference to the original function.
The Wrong Way
This is a common mistake:
# test_bar.py
from unittest.mock import patch
import bar
@patch("foo.get_data", return_value="fake data")
def test_process(mock_get):
result = bar.process()
assert result == "processed fake data"
This test fails, because bar still calls the real get_data()
as it imported from foo before you
patched it. Your patch changed foo.get_data
, not bar.get_data
.
The Right Way
Instead, you need to patch the reference that bar
uses:
@patch("bar.get_data", return_value="fake data")
def test_process(mock_get):
result = bar.process()
assert result == "processed fake data"
Now it works! The unittest.mock.patch
swapped out the object that bar
actually calls.
In a pytest fixture
import pytest
from unittest.mock import patch
@pytest.fixture
def mock_get_data():
with patch("bar.get_data", return_value="mocked") as mock:
yield mock
def test_process(mock_get_data):
from bar import process
assert process() == "processed mocked"
Why This Happens
import
statements in Python work by copying a reference from the source module into the importing
module’s namespace. Once that reference is bound, the importer doesn’t look it up again. This is
different from JavaScript’s live bindings, where imports always reflect the current value of the
export. So when you patch something, you’re not changing a global function everywhere — you’re just
swapping out one module’s reference to it.
Mocking in Python is about replacing references, not rewriting definitions. Always patch in the namespace where the code under test looks up the function, not where it was first written. That mental model will save you from countless hours of “why isn’t my mock working?” frustration — and make your tests more predictable.
Quick Rules of Thumb
- Patch where the function is looked up. Think in terms of “who is calling it,” not “where it lives.”
- Use fully qualified paths. Always patch using the dotted path as it’s imported in the module
under test (e.g.,
bar.get_data
). - Patch as close to the call site as possible. If
bar.process
calls it, patchbar.get_data
. Ifbaz
importsbar
and callsbar.process()
, patch inbaz
if that’s where the call happens.