Fallback cached response mock in Python
Wraps a mock function with a fallback to a real service and caches results to mask transient failures.
Python code
51 linesimport time
from functools import wraps
class CachedMock:
def __init__(self, cache_ttl=5):
self.cache = {}
self.cache_ttl = cache_ttl
def get(self, key):
cached = self.cache.get(key)
if cached and time.time() - cached["timestamp"] < self.cache_ttl:
return cached["value"]
return None
def set(self, key, value):
self.cache[key] = {"value": value, "timestamp": time.time()}
def fallback_cached(mock_func, real_func, cache_ttl=5):
cache = CachedMock(cache_ttl)
@wraps(mock_func)
def wrapper(*args, **kwargs):
cache_key = f"{args}:{kwargs}"
cached_value = cache.get(cache_key)
if cached_value is not None:
return cached_value
try:
value = mock_func(*args, **kwargs)
except Exception:
value = real_func(*args, **kwargs)
cache.set(cache_key, value)
return value
return wrapper
def fake_mock(x):
raise ConnectionError("Mock service down")
def real_service(x):
return f"real:{x * 2}"
mock_with_fallback = fallback_cached(fake_mock, real_service, cache_ttl=3)
if __name__ == "__main__":
print(mock_with_fallback(4))
print(mock_with_fallback(4))
print(mock_with_fallback(5))
Output
real:8
real:8
real:10
How it works
The decorator wraps a mock function and tries it first. If the mock raises an exception, it falls back to the real function. Results are cached by arguments so the mock can return instantly on repeated calls while the cache is valid. The cache uses timestamps to expire entries after a TTL, preventing stale data if the mock recovers or conditions change.
Common mistakes
- Using the cache key without including kwargs, causing collisions
- Caching exceptions instead of falling back and caching the real result
- Forgetting to reset the cache when the underlying service changes
- Not handling None as a valid cached value
Variations
- Use functools.lru_cache with a custom key for simpler caching
- Add circuit-breaker logic to disable the mock for a cooldown period after repeated failures
Real-world use cases
- Testing microservices against mocked external APIs while keeping real fallbacks for integration.
- Degrading gracefully in front of third-party APIs when the primary dependency temporarily fails.
- Caching expensive mock responses in tests to speed up suite execution without hitting network calls.
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.