Python Mock Pitfall: Patch Where It Is Used, Not Where It Is Defined
Originally published at recca0120.github.io from utils import sum, then patch('utils.sum') — and the mock never takes effect. patch('helloworld.sum') and it works. utils.py: def sum(a, b): return a + b helloworld.py: from utils import sum def main(): return sum(1, 2) Writing a test to mock sum: from unittest.mock import patch def test_main(): with patch('utils.sum') as mocked: mocked.return_value = 5 assert main() == 5 # Fails! AssertionError: 3 != 5 main() returns 3, not 5. The mock didn't take effect. from utils import sum does one thing: it copies a reference to the utils.sum function object into helloworld's namespace. After that import, when helloworld calls sum, it's using helloworld.sum — not utils.sum. patch('utils.sum') does replace the sum in the utils module. But helloworld.sum still points at the original function object. It was never touched. Visualized: After patch('utils.sum'): utils.sum ──────→ MockObject ← patch replaced this helloworld.sum ──→ ← this is untouched; main() uses this Patch the module that uses the function — the module under test: def test_main(): with patch('helloworld.sum') as mocked: # patch here instead mocked.return_value = 5 assert main() == 5 # passes patch('helloworld.sum') replaces the sum in helloworld's namespace. That's the one main() calls, so the mock works. This rule is easy to remember: [!IMPORTANT] where the name is used, not where it's defined. Import style Patch target from utils import sum patch('helloworld.sum') import utils patch('utils.sum') With import utils, calling utils.sum(...) looks up sum through the utils module every time — no separate binding is created in helloworld. So patch('utils.sum') works there. # utils.py def sum(a, b): return a + b # helloworld.py from utils import sum def main(): return sum(1, 2) # test_helloworld.py from unittest.mock import patch from helloworld import main def test_main_wrong_patch(): # Wrong: patches utils.sum, but helloworld.sum is unaffected with patch('utils.sum') as mocked: mocked.return_value = 5 result = main() assert result == 5 # AssertionError: 3 != 5 def test_main_correct_patch(): # Correct: patches the binding in the module that uses it with patch('helloworld.sum') as mocked: mocked.return_value = 5 result = main() assert result == 5 # passes # app.py def sum(a, b): return a + b def main(): return sum(1, 2) # test_app.py with patch('app.sum') as mocked: mocked.return_value = 5 assert main() == 5 # passes Same principle: main() looks up sum in the app namespace, so that's where you patch. Python's import mechanism creates a separate binding in the importing module. After from X import Y, the module has its own Y — a separate reference from X.Y. The patch target is always where the call happens, not where the function is defined. Keep that rule in mind and mock-not-working problems essentially disappear. Python docs: unittest.mock — where to patch Python docs: unittest.mock full reference Python import system documentation }}">pytest-mock: Cleaner Mocking With the mocker Fixture
