integration-testing
Integration Testing
Integration tests verify that components work together against real (or close-to-real) dependencies — your service hitting a real Postgres, your consumer reading from a real Kafka, your handler calling a recorded version of a third-party API. They sit between unit tests (single function, mocks for everything) and end-to-end tests (whole system, real users, real network). The goal is to catch the bugs that only show up when wiring meets wiring: schema drift, transaction semantics, serialization mismatches, retry behavior under real timeouts.
The reason integration tests are worth the slowness is that the hardest production bugs almost always live in the seams. Unit tests with mocks tell you "my code does what I think it does"; integration tests tell you "my code and Postgres agree about what happened." Those are different statements.
When to write one
Reach for an integration test when the behavior you care about depends on something outside your code's control:
- The database (migrations applied, indexes in place, transaction isolation behaving)
- An HTTP boundary (request parsing, status codes, content negotiation, auth middleware)
- A queue or stream (delivery semantics, ordering, ack/nack, DLQ)
- A third-party API (auth flow, rate limits, error shapes — usually with a recorded fake)
- Cross-component flow (request → handler → repo → DB → outbox → publisher)
If you can express the bug as "X works in isolation but breaks when wired to Y," you want an integration test. If the only thing you're checking is f(x) == y, write a unit test instead — they're 100× faster and don't deserve the integration tax.