How to Mock Cache Tag Invalidation in Python
Use unittest.mock.patch with wraps to verify tagged cache entries are invalidated correctly.
Python code
32 linesimport unittest
from unittest.mock import patch
def get_cached_data(cache, key):
"""Return data from cache if present and valid, else None."""
if cache.get(key, {}).get("valid", False):
return cache[key]["data"]
return None
def invalidate_tag_mock(cache, tag):
"""Invalidate all cache entries with the given tag."""
keys_to_invalidate = [k for k, v in cache.items() if tag in v.get("tags", [])]
for key in keys_to_invalidate:
cache[key]["valid"] = False
class TestCacheInvalidation(unittest.TestCase):
def test_invalidates_tagged_entries(self):
cache = {
"user:1": {"data": "alice", "tags": ["user"], "valid": True},
"user:2": {"data": "bob", "tags": ["user", "admin"], "valid": True},
"post:1": {"data": "hello", "tags": ["post"], "valid": True},
}
with patch("__main__.get_cached_data", wraps=get_cached_data) as mock_get:
invalidate_tag_mock(cache, "user")
self.assertIsNone(mock_get(cache, "user:1"))
self.assertIsNone(mock_get(cache, "user:2"))
self.assertEqual(mock_get(cache, "post:1"), "hello")
mock_get.assert_called_with(cache, "post:1")
if __name__ == "__main__":
unittest.main(verbosity=2)
Output
test_invalidates_tagged_entries (__main__.TestCacheInvalidation.test_invalidates_tagged_entries) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
How it works
The patch decorator wraps get_cached_data with a MagicMock while preserving the original function via wraps=get_cached_data. The test then calls invalidate_tag_mock to flip the valid flag for all entries whose tags include "user". After invalidation, get_cached_data returns None for the user entries because their valid flag is now False, while the post entry remains untouched. The final assert_called_with verifies the mock was invoked with the correct arguments, confirming the patched function behaves as expected.
Common mistakes
- Forgetting to pass `wraps=get_cached_data`, so the original function is completely replaced instead of wrapped.
- Checking equality with `==` on mocked call arguments instead of using `assert_called_with`.
- Not restoring the original function after the `with` block, which can leak mocks into other tests.
Variations
- Use `patch.object` to patch a method on a class or object instance.
- Use `assert_any_call` to verify the function was called with at least one matching argument set.
Real-world use cases
- Testing a cache invalidation service that clears entries on user update events in a microservices architecture.
- Verifying that a tag-based purge works correctly when regenerating cached pages after content changes.
- Confirming stale Redis cache entries are marked invalid during a deployment rollback routine.
Sponsored
More from Caching & Redis
- Cache Asides in Python with a Read-Through Loader easy
- Cache Data in Redis with Python easy
- Cache Penetration Null Object Mock in Python medium
- Cache Stampede Prevention with SingleFlight in Python medium
- Cache Warming with Python: Preload Hot Keys easy
- Coalescing duplicate in-flight requests: one shared result for concurrent callers hard
Keep learning
Related tutorials and quizzes for this topic.