1.The test database lifecycle, and transaction behaviour per test class
Your tests never touch the real database. Django creates a second one named `test_` plus your real database name, runs every migration into it, runs the suite, and destroys it at the end — pass or fail. Inside that database, the class you inherit from decides how each test is cleaned up. `TestCase` wraps each test in a transaction and rolls it back, which is fast. `TransactionTestCase` lets the test really commit and then truncates every table, which is slow but is the only way to test anything that depends on a commit actually happening.
Key features
- The test database is `test_` + your `NAME`, created before the suite and destroyed after it — "regardless of whether the tests pass or fail"
- `TestCase` "encloses the test code in a database transaction that is rolled back at the end of the test"; `TransactionTestCase` "resets the database after the test runs by truncating all tables"
- `--keepdb` preserves the test database between runs and applies any new migrations to it, turning a 40-second migrate into nothing
- Django runs all `TestCase` subclasses first, then `SimpleTestCase`/`TransactionTestCase`, then plain `unittest.TestCase` — order is a grouping, not alphabetical
- In pytest-django a test has **no** database access until it is marked `@pytest.mark.django_db` or requests the `db` fixture
- `@pytest.mark.django_db(transaction=True)` is the `TransactionTestCase` equivalent; without it "transaction operations are noops during the test"
Which class (or marker) to reach for, and what it costs
- TestCase — rollback
- BEGIN before the test, ROLLBACK after it
- Nothing is ever committed to the test database
- `on_commit()` callbacks never fire on their own
- A second connection cannot see the rows this test made
- Fast: no table truncation between tests
- TransactionTestCase — truncate
- The test really commits
- Every table is truncated afterwards
- `on_commit()` fires, locks really block
- A second connection sees the committed rows
- Slow: pay a truncate per test, so use it deliberately
Command
python manage.py test --keepdb # reuse test_<name>, apply new migrations
python manage.py test --parallel # one test database per process
pytest --reuse-db # pytest-django's equivalent of --keepdbDjango builds `test_<your database>`, migrates it, and destroys it after the run — use `--keepdb` (or `pytest --reuse-db`) so you stop paying for the migrate every time. Inside it, `TestCase` rolls back and `TransactionTestCase` truncates. Stay on the fast one until the thing under test *is* the commit: reach for `captureOnCommitCallbacks(execute=True)` for `on_commit` side effects, and only pay for `transaction=True` when a second connection has to see committed rows.