How to mock an external service in Python with an anti-corruption facade

This code implements an anti-corruption facade that mocks an external API, allowing client code to interact with a simulated service while keeping the same interface.

Easy Python 3.9+ Aug 9, 2026 Microservices patterns 11 views 0 copies

Python code

34 lines
Python 3.9+
class AntiCorruptionFacade:
    """Mocks a real API while keeping the same interface."""
    
    def __init__(self, data_store):
        self._data_store = data_store
        self._calls = []
    
    def get_user(self, user_id):
        self._calls.append(f"get_user({user_id})")
        return self._data_store.get(user_id, {}).copy()
    
    def update_user(self, user_id, data):
        self._calls.append(f"update_user({user_id})")
        self._data_store[user_id] = data
        return True
    
    def get_calls(self):
        return self._calls.copy()

if __name__ == "__main__":
    # Simulate the external service's data
    external_store = {
        1: {"name": "Alice", "email": "alice@example.com"},
        2: {"name": "Bob", "email": "bob@example.com"}
    }
    
    # Client code uses the facade as if it's the real service
    facade = AntiCorruptionFacade(external_store)
    
    print("Initial user 1:", facade.get_user(1))
    print("Updating user 2:", facade.update_user(2, {"name": "Robert", "email": "robert@example.com"}))
    print("Updated user 2:", facade.get_user(2))
    print("Missing user 99:", facade.get_user(99))
    print("Call log:", facade.get_calls())

Output

stdout
Initial user 1: {'name': 'Alice', 'email': 'alice@example.com'}
Updating user 2: True
Updated user 2: {'name': 'Robert', 'email': 'robert@example.com'}
Missing user 99: {}
Call log: ['get_user(1)', 'update_user(2)', 'get_user(2)', 'get_user(99)']

How it works

The AntiCorruptionFacade wraps a simple dictionary as a stand-in for an external API, exposing methods that return dicts just like a real client would. It records every call in _calls, enabling assertions in tests. The get_user method uses .get(user_id, {}) to safely handle missing keys, returning an empty dict instead of raising a KeyError. update_user writes directly to the store and returns True to mimic a success response. This pattern isolates client code from the actual external service, making it easy to swap mocks for real implementations later.

Common mistakes

  • Not copying the returned dict, allowing callers to mutate internal state.
  • Forgetting to track calls, making it hard to verify interactions.
  • Raising exceptions for missing keys instead of returning defaults.

Variations

  1. Use a `Mock` from `unittest.mock` to dynamically record calls and set return values.
  2. Return a custom response object instead of a plain dict if the real API uses one.

Real-world use cases

  • Testing microservice integrations without hitting the real third-party API.
  • Simulating slow or flaky services in local development to ensure graceful degradation.
  • Contract testing where the facade must match the external service's interface exactly.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Microservices patterns

Related tutorials and quizzes for this topic.