skip to content

Why can a value stored in Django's cache by one test still be there in the next test, and how do you stop it?

level: middleimportance: should knowfreq 40%

answer

  1. rollback covers the database only
  2. process memory outlives each test
  3. clear per test, or a dedicated backend
  4. same LOCATION shares one store

basics

~20 s

Django resets the database and the mail outbox between tests but never the cache backends, and the default LocMemCache lives for the whole test process. Call cache.clear() per test or override CACHES with a dedicated backend.

solid answer

~40 s

Django's test cases isolate the database (rollback or table flush) and empty `mail.outbox`, but nothing clears cache contents between tests. The default `CACHES` entry is `LocMemCache`, which keeps its data in module-level memory for the life of the process, so a value cached by one test is visible to later tests in that process and results depend on test order. Call `cache.clear()` in `setUp()` (or register it with `addCleanup`), or override `CACHES` for the class: the `setting_changed` receiver rebuilds the cache handlers so `django.core.cache.cache` resolves the new backend. Give an overriding `LocMemCache` its own `LOCATION`, because instances with the same location share one store. Process-level caches outside the cache framework, such as `ContentType.objects.clear_cache()`, need clearing too.

code

python · 22 lines
python
from django.core.cache import cache
from django.test import TestCase, override_settings

from profiles.services import get_profile_summary


@override_settings(
    CACHES={
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
            'LOCATION': 'profile-summary-tests',
        }
    }
)
class ProfileSummaryCacheTests(TestCase):
    def setUp(self):
        cache.clear()

    def test_first_call_is_a_miss(self):
        self.assertIsNone(cache.get('profile-summary:1'))
        get_profile_summary(user_id=1)
        self.assertIsNotNone(cache.get('profile-summary:1'))

go deeper

for a junior

Know that Django's per-test database reset does not touch the cache, and that calling cache.clear() in setUp keeps tests independent.

for a middle

Explain why the default LocMemCache survives across tests, how override_settings on CACHES rebuilds the handlers, and why the override needs its own LOCATION.

for a senior

Track down order-dependent failures to process-level caches, including ContentType, Site and lru_cache helpers, and wire setting_changed receivers where state derives from settings.

for a principal

Set suite policy for shared state: which caches tests may use, how parallel workers are kept apart, and where DummyCache hides behaviour that production depends on.

## What Django resets between tests, and what it does not Django's test case classes reset a specific, limited set of state. Everything else survives from one test to the next within the same process. | State | Reset for each test? | How | |---|---|---| | database rows | yes | `TestCase` rolls back; `TransactionTestCase` flushes tables | | `django.core.mail.outbox` | yes | a new empty list before each test | | settings changed with `override_settings` | yes | restored when the override exits | | contents of the cache framework (`CACHES`) | **no** | nothing clears them | | `ContentType` and `Site` lookup caches | **no** | nothing clears them | | settings assigned directly | **no** | never restored | The cache is the one people forget, because it looks like part of the database but is not in a transaction. ## Why the default cache leaks When a project does not configure `CACHES`, Django uses `django.core.cache.backends.locmem.LocMemCache`. Its storage is a **module-level dictionary keyed by `LOCATION`** (the empty string by default). That dictionary lives as long as the Python process: - a test that caches `profile:42` leaves it there for every later test in the process; - a later test that expects a cache miss gets a stale hit, and passes or fails depending on **test order**; - with `--parallel`, each worker process has its own memory, so failures depend on which tests share a worker and look random; - an external cache server configured for tests is worse: it is shared by every worker and survives between runs. The symptom is the classic one: the test passes alone and fails in the full suite. ## Three ways to isolate 1. **Clear per test.** Call `cache.clear()` in `setUp()`, or register `self.addCleanup(cache.clear)` so the next test starts clean even if this one fails. For several aliases, loop over `caches.all()`. Never point tests at a cache that anything else uses: `clear()` removes every key in it. 2. **Give the class its own backend.** `@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'profile-tests'}})`. Django's `setting_changed` receiver for `CACHES` closes the existing handlers and rebuilds them, and the module-level `cache` proxy looks up the current handler on each use, so code importing `from django.core.cache import cache` sees the new backend. Use a **distinct `LOCATION`**: two `LocMemCache` instances with the same location share one dictionary, so overriding with the default location keeps the old data. 3. **Switch caching off.** `django.core.cache.backends.dummy.DummyCache` implements the cache API without storing anything. It suits tests of code that must work on a cold cache, but it cannot test caching behaviour itself. ## Caches outside the cache framework Some process-level caches are ordinary Python state: - `ContentType.objects` caches content-type lookups; clear with `ContentType.objects.clear_cache()`. - The sites framework caches the current site; clear with `Site.objects.clear_cache()`. - Your own `functools.lru_cache` functions keep results until `cache_clear()` is called; if they depend on a setting, connect a `setting_changed` receiver so `override_settings` clears them automatically. These also shift query counts, which is how many teams discover them. ## What interviewers listen for That the candidate knows the database rollback does not cover the cache, that the default backend is process-wide memory, and that they can pick between clearing, a dedicated backend and `DummyCache` — ideally noting the shared-`LOCATION` trap.

  • Why does overriding CACHES with a plain LocMemCache entry sometimes still show old values?
    `LocMemCache` stores data in a module-level dictionary keyed by `LOCATION`, and an entry without `LOCATION` uses the empty string — the same key the project's default entry uses. The rebuilt handler therefore attaches to the same dictionary. Give the override its own `LOCATION`, or clear the cache in `setUp()`.
  • When is DummyCache the right test backend, and when is it wrong?
    It is right when the code under test must behave correctly with no cached data — every read is a miss and nothing is stored. It is wrong for tests of the caching itself, such as invalidation on save, because nothing is ever stored to observe.

saying these in an interview costs you the question

  • TestCase's rollback also undoes values written to the cache
  • Django clears the default cache before every test
  • Overriding CACHES with any LocMemCache entry always gives an empty cache
  • Pointing tests at a shared cache server is safe if each test clears it
  • DummyCache is the best default because it still exercises caching logic