Matched signals
- duplicate key value
- UniqueConstraintViolation
- IntegrityError
- duplicate entry
- expected count to be
- PG::UniqueViolation
- SQLSTATE 23505
- violates unique constraint
Test database state pollution between tests
What this failure means
Tests are polluting a shared database with committed data that later tests did not expect to find. Tests pass in isolation but fail when the full suite runs, or fail only in certain orderings.
Symptoms
Faultline looks for one or more of these log fragments:
duplicate key value
UniqueConstraintViolation
IntegrityError
duplicate entry
expected count to be
PG::UniqueViolation
SQLSTATE 23505
violates unique constraint
Diagnosis
Each test is leaving committed rows, modified schema state, or other database changes that subsequent tests encounter as unexpected pre-existing data.
Typical symptoms:
already existsorduplicate keyerrors in tests that insert seed data.- Counts or queries that return more rows than expected.
- Tests pass individually but fail together or in a different order.
Fix steps
-
Wrap each test in a database transaction that is rolled back after the test:
-
Rails:
use_transactional_fixtures = true(default in RSpec Rails). -
Go (sqlx/pgx): begin a transaction in
TestMainort.Cleanup, pass it to the test, and roll back after:tx, _ := db.BeginTx(ctx, nil) defer tx.Rollback() -
Django:
TestCasewraps each test in a transaction automatically. UseTransactionTestCaseonly when testing transaction-specific behaviour.
-
-
For tests that cannot use transactions (e.g., testing COMMIT behaviour), use a dedicated test database and truncate all tables in a setup/teardown hook:
TRUNCATE TABLE users, orders, events RESTART IDENTITY CASCADE; -
Check for tests that use
TRUNCATEorDELETEon shared tables and commit — these affect concurrent parallel tests. Namespace test data with a unique run ID or isolate these tests in a separate database. -
Confirm the database is seeded once before the test suite and not accumulating inserts across test runs. Use a per-test factory (e.g.,
factory_boy,FactoryBot, Go test helpers) that inserts and cleans up its own data. -
Run tests in random order to surface all implicit ordering dependencies:
pytest --randomly-seed=<N> go test -shuffle=on ./...
Validation
- Run the full suite multiple times in random order and confirm zero failures caused by unexpected pre-existing data.
- Confirm each test asserts only on data it created, not on total row counts.
Why it matters
When tests commit data to a shared database without cleaning up, subsequent tests see unexpected rows. The failure mode is subtle: tests pass in local development (where the database is often wiped between sessions) but fail in CI (where the database persists across the full suite run).
Prevention
- Treat database isolation as a first-class constraint: every test must leave the database in the same state it found it.
- Use transactional test helpers by default and reserve non-transactional tests for cases that genuinely need committed data.
- Track and review any test that uses
TRUNCATEorDELETEoutside a rollback boundary — these affect concurrent parallel tests.
Try it locally
go test -shuffle=on ./...
pytest --randomly-seed=<N>
go test -shuffle=on ./...
How Faultline detects it
Use faultline explain database-test-isolation to see the full playbook.
faultline analyze build.log
faultline explain database-test-isolation
Generated from playbooks/bundled/log/test/database-test-isolation.yaml. Do not edit directly.