How to Mock Cache Tag Invalidation in Python

Use unittest.mock.patch with wraps to verify tagged cache entries are invalidated correctly.

Medium Python 3.9+ Aug 9, 2026 Caching & Redis 14 views 0 copies

Python code

32 lines
Python 3.9+
import 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

stdout
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

  1. Use `patch.object` to patch a method on a class or object instance.
  2. 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

Run this sample

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

Open editor

More from Caching & Redis

Related tutorials and quizzes for this topic.