← Python Code Python's Hidden Traps
Browse Python Concepts

Mocking Pitfalls — Patching the Wrong Namespace

Mental Model

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.

Rule: When using mock.patch, always patch the target namespace where the object is imported and used, not where it is defined.

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?

Broken code
Python
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())
Predict what the print statement outputs. Does it print 'MOCKED_CHARGE' or does it hit the real implementation and output 'REAL_CHARGE'?

The Output

What actually happens
REAL_CHARGE

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

Corrected pattern
Python
# 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.

Key Takeaway

When using mock.patch, always patch the target namespace where the object is imported and used, not where it is defined.
Common mistake: Engineers mock where a symbol is defined rather than where it is consumed, then wonder why the real code still runs.