Skip to Content
GuidesTestingTest Runners

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:

  1. Drops stale test databases before creation (no confirmation prompt)
  2. Installs PostgreSQL extensions detected in your migrations
  3. Fixes inconsistent migration history automatically
  4. Passes autoclobber=True to 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.FastTestRunner

Trade-offs:

SmartTestRunnerFastTestRunner
SpeedNormal~10× faster
PostgreSQL featuresFullNone
pgvector / uuid PKWorksNot supported
Use forIntegration testsPure unit tests

Importing

from django_cfg import FastTestRunner from django_cfg.testing.runners import FastTestRunner

What 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 exist

Fix: 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_options

Fix: 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_check

Monitor 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:

  1. Sets databases = {"default", "monitor"} so Django allows queries to both aliases
  2. Removes the django_cfg_monitor routing rule in setUp / restores it in tearDown — forcing all monitor models to use the default connection 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

TAGS: SmartTestRunner, FastTestRunner, MigrationManager, test runners, migration fix, MonitorTestCase, monitor database DEPENDS_ON: [overview, test-db-command, migration-internals]

Last updated on