# 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.

Most Django projects run their tests with the settings they use for development, plus a few overrides. Some of what's useful while developing costs time in every test: django-debug-toolbar instrumenting requests, `DEBUG = True` recording every SQL query, and a manifest static files storage looking up hashed file names for every `{% static %}` tag. None of it makes the tests more meaningful.

This guide covers those three. The other settings worth changing for tests have their own guides, listed at the end.

## A TESTING flag

Settings files need to know when they're loaded for tests. django-debug-toolbar's documentation uses:

```python
# settings.py
import os
import sys

TESTING = "test" in sys.argv or "PYTEST_VERSION" in os.environ
```

`manage.py test` puts `test` in `sys.argv`, and pytest 8.2+ sets `PYTEST_VERSION`. If you keep a separate test settings module instead, put the overrides there.

## Keep django-debug-toolbar out of tests

When the toolbar is active for a request, every panel instruments it: the SQL panel wraps the database cursor and records each query with a stack trace, others record templates and their context, cache calls and signals, and the result is rendered into the response. That's useful in a browser and pure overhead in a test.

The toolbar is active when `DEBUG` is on (or your `SHOW_TOOLBAR_CALLBACK` says so) and the request comes from `INTERNAL_IPS`. The test client's requests come from `127.0.0.1`, which is usually in that list. Django's test runner turns `DEBUG` off, but only after the settings module has run, so a block like `if DEBUG: INSTALLED_APPS += ["debug_toolbar"]` still installs it.

Install it only when not testing, as the toolbar's documentation recommends:

```python
# settings.py
if DEBUG and not TESTING:
    INSTALLED_APPS = [*INSTALLED_APPS, "debug_toolbar"]
    MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE]
```

```python
# urls.py
from django.conf import settings

if settings.DEBUG and not settings.TESTING:
    from debug_toolbar.toolbar import debug_toolbar_urls

    urlpatterns = [*urlpatterns, *debug_toolbar_urls()]
```

Recent versions of the toolbar also have a system check (`debug_toolbar.E001`) that fails when it detects tests running with a toolbar setup that would break them.

## Don't run tests with DEBUG = True

Django's test runner sets `DEBUG = False` for the run regardless of your settings, and so does pytest-django. Tests only run with `DEBUG` on when something forces it:

- `python manage.py test --debug-mode`
- `django_debug_mode = true` in pytest-django's configuration, or `keep` with `DEBUG = True` in settings
- `override_settings(DEBUG=True)` on a base test class, or a fixture that sets `settings.DEBUG = True`

With `DEBUG` on, Django wraps every database cursor in `CursorDebugWrapper`, which times each query, renders the SQL with its parameters and appends it to `connection.queries` (up to 9,000 entries per connection). Error responses render the technical debug page, and other code that checks `DEBUG` behaves the way it does on your laptop, not in production.

Remove whatever forces it. In pytest-django the default is already right:

```toml
# pyproject.toml
[tool.pytest.ini_options]
django_debug_mode = false
```

Tests that need debug behaviour can turn it on for themselves with `@override_settings(DEBUG=True)`. Tests that want to look at queries don't need `DEBUG` at all: `assertNumQueries`, `django_assert_num_queries` and `django.test.utils.CaptureQueriesContext` enable query recording just inside their block.

## Use plain static file storage in tests

`ManifestStaticFilesStorage`, and WhiteNoise's `CompressedManifestStaticFilesStorage` built on it, map every `{% static "app.css" %}` to a hashed name like `app.3f2a1c.css` using the `staticfiles.json` manifest that `collectstatic` writes. In tests that means:

- you have to run `collectstatic` before the tests, a slow CI step, or templates fail with `ValueError: Missing staticfiles manifest entry`
- every `{% static %}` tag goes through the manifest lookup

WhiteNoise's middleware adds its own cost: when it's created with `DEBUG` off it walks `STATIC_ROOT` and records every file. Django's test client builds the middleware chain again for every new client, and every test gets a new client, so with a populated `STATIC_ROOT` that scan happens in each test that makes a request.

Django's documentation says this storage typically shouldn't be used when running tests. Use the default storage in test settings:

```python
# settings.py
if TESTING:
    STORAGES = {
        **STORAGES,
        "staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
    }
    MIDDLEWARE = [m for m in MIDDLEWARE if m != "whitenoise.middleware.WhiteNoiseMiddleware"]
```

If your settings don't define `STORAGES` at all, you already have `StaticFilesStorage`, Django's default. Removing the WhiteNoise middleware is only safe if no test requests a static file through Django; most don't. Your test job no longer needs `collectstatic` either.

## The other settings for fast tests

| Setting | In tests | Guide |
|---|---|---|
| `PASSWORD_HASHERS` | `MD5PasswordHasher` | [fast password hasher](https://flakytest.dev/guides/fast-password-hasher-in-tests/) |
| `CACHES` | `LocMemCache` instead of Redis or Memcached | [in-memory cache](https://flakytest.dev/guides/in-memory-cache-in-tests/) |
| `CELERY_TASK_ALWAYS_EAGER` | `True`, with `CELERY_TASK_EAGER_PROPAGATES` | [eager Celery tasks](https://flakytest.dev/guides/celery-eager-in-tests/) |
| `LOGGING` | `WARNING` and above, no debug handlers | [logging in tests](https://flakytest.dev/guides/logging-in-tests/) |

`EMAIL_BACKEND` needs nothing: Django's test runner and pytest-django already switch it to the in-memory backend.

## Keep the tests correct

- Change these in test settings only. Production keeps its manifest storage and `DEBUG = False`.
- Tests that rely on `DEBUG` behaviour (error pages, debug-only views) can override it with `override_settings` on that test.
- With `StaticFilesStorage`, a `{% static %}` tag naming a missing file no longer fails a test. Keep `collectstatic --noinput` in your build or deploy pipeline, which still fails on broken references in CSS, and keep one job with the manifest storage if you rely on tests to catch missing static files.

## How flakytest finds it

flakytest reads your Django settings during the run and measures the time in the code involved:

- **Disable django-debug-toolbar in tests**: `debug_toolbar` is in `INSTALLED_APPS` and its code takes at least 0.2 seconds.
- **Tests run with DEBUG = True**: `DEBUG` is on during the tests and at least 0.3 seconds is spent in `CursorDebugWrapper`.
- **Use plain static file storage in tests**: Django's staticfiles storage or WhiteNoise takes at least half a second.

## Further reading

- [django-debug-toolbar: disable the toolbar when running tests](https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#disable-the-toolbar-when-running-tests-optional)
- [Django: other test conditions (DEBUG in tests)](https://docs.djangoproject.com/en/stable/topics/testing/overview/#other-test-conditions)
- [Django: `test --debug-mode`](https://docs.djangoproject.com/en/stable/ref/django-admin/#cmdoption-test-debug-mode)
- [pytest-django: `django_debug_mode`](https://pytest-django.readthedocs.io/en/latest/usage.html#django-debug-mode-change-how-debug-is-set)
- [Django: ManifestStaticFilesStorage](https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#manifeststaticfilesstorage)
- [Django: the `STORAGES` setting](https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-STORAGES)
