Where should integration tests draw their boundary - at the service, at the database, or at the external dependency?
Show the full answer Hide the answer
The principle
Include what you cannot faithfully fake; exclude what you can.
- Include the real database. Query behaviour, transaction semantics, constraint enforcement and index behaviour are not faithfully reproducible by an in-memory substitute, and the differences are exactly where the bugs are. A test suite that fakes the database tests something else.
- Include the real message broker or workflow engine where the semantics matter — ordering, redelivery, visibility timeouts, durable timers. These behaviours are the reason the component was chosen and a fake does not have them.
- Exclude external third parties, virtualised instead. Testing against them at any volume is slow, rate-limited, non-deterministic and occasionally irreversible.
- Exclude other internal services, covered by contract tests instead — including them turns every test into a distributed system with its own flakiness and its own deployment coupling.
What the virtualised dependencies must simulate
Not just the happy path. Timeouts, slow responses, rate limiting, malformed payloads, success responses containing an error body, and the ambiguous case where a request may or may not have taken effect — which is the category most systems omit and the one that causes duplicates.
The durable-workflow specific case
Testing a workflow that runs for days requires controlling time rather than waiting. The engine must allow timers to be advanced in tests, or the suite either takes days or does not test the timers — and the timers are frequently where the interesting behaviour is.
Also test worker restart mid-workflow, since resumption is the property the engine exists to provide and it is rarely exercised deliberately.
The trade-off
Real dependencies make tests slower and require infrastructure per test run, which pushes toward ephemeral containers and parallelisation.
That cost is worth paying for the database and the engine and is not worth paying for third parties — and the distinction is whether a fake can be faithful, which for a database it cannot and for an HTTP API it largely can, provided the fake is built from recorded real interactions and contract-tested against the provider to detect drift.