Speed up your test suite

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_many loop 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.
  • LocMemCache has 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

Updated October 18, 2026 · Suggest a change