Mocking Pitfalls — Patching the Wrong Namespace
Imagine you want to replace a specific book on a bookshelf. If you go to the library's warehouse and replace the master copy, it won't affect the specific copy already checked out and sitting on your desk. To affect the one on your desk, you need to replace it directly on your desk. Mocking needs to target the specific reference held by the code under test.
The Setup
You have an app module that processes payments using an external library called StripeClient. In your unit tests, you attempt to patch 'stripe.StripeClient' to prevent hitting the real API during a test run, but to your horror, the test actually makes a network call.
What Does This Print?
from unittest.mock import patch
import sys
class StripeClient:
def charge(self): return "REAL_CHARGE"
sys.modules['stripe'] = sys.modules[__name__]
sys.modules['app'] = type('App', (), {'run_payment': lambda: StripeClient().charge()})
with patch('stripe.StripeClient') as mock_client:
mock_client.return_value.charge.return_value = "MOCKED_CHARGE"
import app
print(app.run_payment())
The Output
The patch failed because it targeted the source namespace stripe.StripeClient. Since the app module had already imported the StripeClient class direct reference during import time, the reference in app remained pointing to the original, un-mocked class definition.
Why Python Does This
When you write from stripe import StripeClient in app.py, Python looks up stripe in sys.modules, finds StripeClient, and binds the name StripeClient in app's local namespace directly to that class object. If you later patch stripe.StripeClient, you are changing the attribute on the stripe module object. However, app.py has its own reference to the original class object. To affect app.py, you must patch the namespace where the name is resolved during execution—which is app.StripeClient.
The Fix
# Patch the exact namespace where the import is bound and consumed
with patch('app.StripeClient') as mock_client:
mock_client.return_value.charge.return_value = "MOCKED_CHARGE"
# Now, app.StripeClient has been redirected to the Mock object
Patching 'app.run_payment' (or 'app.StripeClient' if 'StripeClient' was directly imported into 'app' and used that way) replaces the name binding in the consuming module's namespace. This ensures that when the 'app' module looks up 'StripeClient' (or the 'charge' method indirectly), it finds the mock object instead of the original, as the import already happened and fixed the reference.
How This Fails in Real Systems
A developer wrote integration tests for an SMS notification worker. They patched twilio.rest.Client. Because the notification module used direct import statements, the test suite ended up dispatching over 15,000 real text messages to staging sandbox phones, exhausting the company's monthly budget in minutes.