Test Runners
Django-CFG provides two test runners and a migration utility that work together to eliminate the most common test setup failures.
SmartTestRunner
The default runner. Used automatically when you run python manage.py test.
What it does:
- Drops stale test databases before creation (no confirmation prompt)
- Installs PostgreSQL extensions detected in your migrations
- Fixes inconsistent migration history automatically
- Passes
autoclobber=Trueto Django so CI never blocks on stdin
Importing
# Lazy import (recommended)
from django_cfg import SmartTestRunner
# Direct import
from django_cfg.testing.runners import SmartTestRunner
# In settings.py (without django-cfg config)
TEST_RUNNER = "django_cfg.testing.runners.SmartTestRunner"Preserve Between Runs
To skip the automatic cleanup and reuse an existing test database:
python manage.py test --keepdb--keepdb can cause InconsistentMigrationHistory errors when migrations change. Drop the test DB manually if that happens: python manage.py test_db cleanup --force
FastTestRunner
Switches all databases to SQLite in-memory. Use for unit tests that don’t need PostgreSQL.
python manage.py test --testrunner=django_cfg.testing.runners.FastTestRunnerTrade-offs:
| SmartTestRunner | FastTestRunner | |
|---|---|---|
| Speed | Normal | ~10× faster |
| PostgreSQL features | Full | None |
| pgvector / uuid PK | Works | Not supported |
| Use for | Integration tests | Pure unit tests |
Importing
from django_cfg import FastTestRunner
from django_cfg.testing.runners import FastTestRunnerWhat SmartTestRunner Fixes Automatically
SmartTestRunner handles migration inconsistencies internally as part of setup_databases(). No manual intervention is needed.
What Gets Fixed
Missing PostgreSQL extensions
Problem:
django.db.utils.ProgrammingError: type "vector" does not existFix: Extensions are installed after the empty DB is created but before migrations run, using install_extensions_on() from django_cfg.testing.runners.utils.
Inconsistent migration history
Problem:
InconsistentMigrationHistory: ai_chat.0001_initial applied before
workspaces.0002_alter_workspacemember_optionsFix: SmartTestRunner patches MigrationLoader.check_consistent_history to skip the error and patches _check_lazy_references to suppress “app isn’t installed” errors from migration state snapshots. Both patches are applied only during setup_databases() and restored immediately after.
Swappable dependency order
Problem: Django doesn’t always respect swappable_dependency() ordering when creating test databases.
Fix: The consistency-check patch allows migrate to proceed with the correct plan rather than blocking on a history error.
# What the bypass does internally (from SmartTestRunner.setup_databases)
original_check = MigrationLoader.check_consistent_history
def patched_check(self, connection):
try:
return original_check(self, connection)
except Exception as e:
if 'InconsistentMigrationHistory' in str(type(e).__name__):
sys.stderr.write(f"⚠️ Using smart migration manager to fix: {e}\n")
return
raise
MigrationLoader.check_consistent_history = patched_check
try:
old_config = super().setup_databases(autoclobber=True, **kwargs)
finally:
MigrationLoader.check_consistent_history = original_checkMonitor Database in Tests
This section applies if you use the built-in monitor app (django_cfg.apps.system.monitor).
The monitor database alias points to the same physical PostgreSQL database as default, but uses a separate connection (conn_max_age=0) so error records written by ServerEvent.record() are never rolled back by ATOMIC_REQUESTS.
In tests this split causes cross-connection FK violations: a User created inside a TestCase transaction on default is invisible to the monitor connection, breaking any ForeignKey(User) constraints on monitor models.
MonitorTestCase
All tests that touch monitor models should inherit from MonitorTestCase:
from django_cfg.apps.system.monitor.tests.conftest import MonitorTestCase
class MyEventTest(MonitorTestCase):
def test_something(self):
user = make_user()
event = make_event(user=user) # no FK violation
self.assertEqual(event.user, user)MonitorTestCase does two things:
- Sets
databases = {"default", "monitor"}so Django allows queries to both aliases - Removes the
django_cfg_monitorrouting rule insetUp/ restores it intearDown— forcing all monitor models to use thedefaultconnection and share the test transaction with auth models
How TEST MIRROR works
DatabaseSettingsGenerator automatically detects when the monitor alias points to the same physical DB as default and sets:
DATABASES["monitor"]["TEST"]["MIRROR"] = "default"Django then reuses default’s test DB for the monitor alias — no separate test DB is created, and django_cfg_monitor migrations run only once (on default).
The DatabaseRouter is patched to allow django_cfg_monitor app migrations to run on default when monitor is a MIRROR alias. No manual configuration is needed.
See Also
- Overview — quick start and what’s automatic
- test_db Command — CLI for manual database management
- Migration Internals — root cause analysis of test DB failures
TAGS: SmartTestRunner, FastTestRunner, MigrationManager, test runners, migration fix, MonitorTestCase, monitor database DEPENDS_ON: [overview, test-db-command, migration-internals]