--- name: postgres-integration-testing description: Testing against a real Postgres rather than a mock, with isolation that survives parallel runs. when_to_use: You are the tester covering code that issues SQL. tags: [backend, testing, postgres] --- # Mock the network, never the database A mocked database agrees with whatever the code believed. Constraints, cascades, transaction visibility, type coercion and the SQL itself are exactly the things that break, and a mock asserts none of them. ## Isolation is the whole problem Parallel tests sharing a database interfere in ways that look like flaky code. Three workable strategies: 1. **A fresh migrated database per test.** Cleanest and slowest. Right when the suite is small or the schema is the thing under test. 2. **A transaction per test, rolled back.** Fast and well isolated, but the code under test cannot manage its own transactions — which rules it out for anything testing commit behaviour. 3. **Unique keys per test.** Every row keyed by a per-test uuid, no cleanup. Scales well, and the leftover rows are useful when something fails. Pick one per suite and say which. Mixing them produces the failures each was meant to prevent. ## Test what only a real database can tell you - **Constraints fire.** A unique violation, a FK failure, a check constraint — assert the error, not just the happy path. - **Cascades do what you think.** `ON DELETE CASCADE` reaching further than intended is a data-loss bug that only a real delete reveals. - **The migration applies to a populated table.** A migration tested on an empty database has not been tested. Insert rows first, then migrate. - **Concurrent claims are atomic.** For any `FOR UPDATE SKIP LOCKED` queue, run two workers and assert the row was claimed once. ## Assert on the database, not only the return value A handler can return 200 while writing nothing. Read the row back and check it. The class of bug where the code "succeeded" and the data did not change is invisible to a test that only inspects the response. ## Keep it fast enough to run An integration suite nobody runs protects nothing. Share one container across the suite rather than per test, run in parallel with proper isolation, and keep fixtures small — realistic in shape, not in volume.