Use an in-memory cache in tests
Tests that talk to a real Redis or Memcached pay a network round trip per cache call and share state between tests. Use Django's LocMemCache or fakeredis in tests and clear it per test.
When test settings point the cache at a real Redis or Memcached server, every cache.get() and cache.set() in the code under test is a network call: send the key and value over a socket, wait for the server, read the reply. Each one is fast on its own, but code that caches per object or per request makes many of them, and a test suite runs that code thousands of times.
A real cache server also makes tests less reliable. It keeps state between tests (and between runs), so a value cached by one test changes what the next test sees, and pytest-xdist workers sharing one Redis database step on each other's keys.
Why a real cache server is slow in tests
- A round trip per call. Even on localhost, a call goes through the kernel's network stack twice. In CI the server is often a service container, and a
get_manyloop or a cache-per-row pattern turns into hundreds of round trips per test. - Connections. Clients connect lazily, and code that creates a new client per call reconnects every time.
An in-memory cache keeps values in a Python dict in the test process. There's no socket, and nothing survives the process.
Django: LocMemCache in test settings
# settings/test.py
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
},
}
If your settings define several caches ("default", "sessions", "ratelimit"), override each of them. Give each one a different LOCATION so they stay separate:
CACHES = {
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "default"},
"sessions": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "sessions"},
}
LocMemCache supports the whole Django cache API (get, set, get_many, incr, touch, timeouts), and each pytest-xdist worker gets its own.
DummyCache is even cheaper, but it never stores anything: cache.get() always returns None. Only use it if no test depends on a value being cached.
When you need Redis features: fakeredis
Code that uses Redis beyond the Django cache API (django-redis's cache.lock() or ttl(), sorted sets, raw redis client calls) needs something that speaks Redis. fakeredis implements the Redis commands in Python, in memory.
With Django's built-in Redis backend:
# settings/test.py
from fakeredis import FakeConnection
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://localhost:6379",
"OPTIONS": {"connection_class": FakeConnection},
},
}
With django-redis, pass it through the connection pool instead:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://localhost:6379/0",
"OPTIONS": {"CONNECTION_POOL_KWARGS": {"connection_class": FakeConnection}},
},
}
Outside Django, inject the client and give tests a fake one:
import fakeredis
import pytest
from myapp.cache import RateLimiter
@pytest.fixture
def redis_client():
client = fakeredis.FakeRedis()
yield client
client.flushall()
def test_rate_limit(redis_client):
limiter = RateLimiter(redis_client, limit=2)
assert limiter.allow("alice")
assert limiter.allow("alice")
assert not limiter.allow("alice")
For asyncio code, use fakeredis.FakeAsyncRedis. To make several clients share data (an app client and a test client), pass them the same fakeredis.FakeServer().
Clear the cache between tests
An in-memory cache still lives as long as the test process, so a value set in one test is there in the next. Clear it in an autouse fixture:
# conftest.py
import pytest
from django.core.cache import caches
@pytest.fixture(autouse=True)
def clear_caches():
yield
for cache in caches.all():
cache.clear()
With unittest-style Django tests, do the same in setUp of a shared base class or in a tearDown. Clearing an in-memory cache is cheap, so doing it for every test costs next to nothing.
Keep the tests correct
- Clear the cache between tests (
cache.clear()in an autouse fixture) so tests stay independent. - Keep a few tests against a real server for code that depends on its exact behaviour: eviction, Lua scripts, cluster or replication, persistence. Mark them (for example
@pytest.mark.redis) and run them in a CI job that has a Redis service. LocMemCachehas no key length or character limits, while Memcached rejects keys over 250 characters or with spaces. Django warns about such keys (CacheKeyWarning) with every backend; turn that warning into an error in tests if you deploy on Memcached.
How flakytest finds it
flakytest puts time spent in Redis and Memcached clients and Django cache backends into a cache category. When that adds up to at least 0.5 seconds and 1% of test time, it suggests Use an in-memory cache in tests. If your Django settings use a Redis or Memcached backend, the suggestion shows the LocMemCache setting to use, and it lists the lines of your code that make the cache calls.
Further reading
Related guides
- Django test settings for speed (debug toolbar, DEBUG, static storage): Development settings slow Django tests: keep django-debug-toolbar out, don't force DEBUG = True, and use StaticFilesStorage instead of manifest or WhiteNoise storage.
- Keep tests independent of each other: Tests should pass alone, in any order and in parallel. Find order-dependent pytest tests with pytest-randomly and pytest-xdist, and fix the shared state behind them.
- Mock and block real network calls in tests: Real HTTP calls make pytest suites slow and flaky. Mock at the transport with responses, respx, pytest-httpx, aioresponses, moto or vcrpy, then block the network with pytest-socket.
- Run tests in parallel with pytest-xdist: Run pytest in parallel on every CPU with pytest-xdist and -n auto. Per-worker databases with pytest-django, what breaks in parallel, pytest-randomly, and the --dist modes.
Updated October 18, 2026 · Suggest a change