Skip to content
errium

Testing layout

How the suite is organised, and why conftest configures Django.

The suite splits along one line: does this test need a running app?

tests/unit/

Pure unit tests against classifiers, normalizers, and the formatter. No app, no client, no framework wiring. A classifier is just an object with a classify method, so testing it means constructing an exception and asserting on the ClassifiedError that comes back.

This is where edge cases belong — the duplicate-marker matching in DatabaseExceptionClassifier, the template lookup order in ValidationNormalizer, the 5xx sanitization rule in DefaultFormatter.

tests/integration/

Each of these drives a real app end-to-end through the adapter’s actual wiring, and asserts on the full JSON response shape:

AdapterClient
FastAPIfastapi.testclient.TestClient
Flaskflask.Flask.test_client()
Django Ninjaninja.testing.TestClient
DRFrest_framework.test.APIRequestFactory against @api_view views
errium_djangodjango.test.Client against a test URLconf (tests/django_urlconf.py) with override_settings

Going through the real integration point is the whole value here. A unit test can confirm FastAPIValidationErrorClassifier returns a 422; only an integration test confirms that RequestValidationError actually reaches it, which — given that it bypasses middleware entirely — is the part most likely to break.

A few suites cut across adapters: test_never_raises.py drives hostile exceptions (a raising __str__, unprintable arguments, a 500 KB message) through all four, and test_logging_and_trace.py asserts log records, the X-Trace-Id header and request-id handling on each.

tests/conftest.py

Configures a minimal Django settings module — including REST_FRAMEWORK["EXCEPTION_HANDLER"] — and calls django.setup() before collection.

This is not optional setup. Importing errium_ninja or errium_drf pulls in django.http and rest_framework, and both raise ImproperlyConfigured if Django settings have not been configured. Without conftest, collection fails at import time, before a single test runs.

It is a no-op for the FastAPI and Flask suites, which have no Django dependency.

Running

uv run pytest
uv run pytest --tb=short

CI runs ruff check, ruff format --check, mypy, and pytest across Python 3.11 and 3.12 on every push and pull request to main. The source is checked under mypy --strict.