Skip to content

Integration Test Automation

Integration testing explains what this level tests. This page is about automating it: making tests that cross real seams fast enough, isolated enough and reliable enough to run on every commit.

graph LR
    T[Test] --> APP[Application code]
    APP --> DB[(Real database in a container)]
    APP --> BROKER[Real or faithful broker]
    APP --> EXT[Third party, simulated]
    T --> ASSERT[Assert on returned values and on real persisted state]

Containerised dependencies

The practice that made this level practical: start the real database, broker or cache as a container from within the test run.

Approach Fidelity Cost
Real engine in a container per suite High, it is the actual engine Seconds of startup, reusable across tests
Shared long-lived environment High Collisions, ordering dependencies, flakiness
In-memory substitute of a different engine Low, different dialect and constraints Fast, and misleading

Prefer the first. The third is the classic false economy: the test suite passes against a different database than the one production runs, so dialect, constraint and migration defects survive to release.

Isolation between tests

Integration tests share expensive state, which is where flakiness comes from.

graph TD
    S[Isolation strategy] --> TX["Transaction per test, rolled back at the end"]
    S --> TRUNC["Truncate tables between tests"]
    S --> UNIQ["Unique data per test, no cleanup needed"]
    S --> FRESH["Fresh schema per suite, parallel workers each with their own"]
Strategy Speed Caveat
Transaction rollback Fastest Cannot test code that commits or uses its own transactions
Truncate between tests Fast Must cover every table, including new ones
Unique data per test Fast, parallel-friendly Requires discipline in every test
Schema per worker Slower to start The most robust for parallel runs

Whatever the choice, no test may depend on data left by another. Order dependence is a flaky test waiting for a parallel run.

What to make real, again

Dependency Default
Your database Real, in a container, with the real migrations applied
Your message broker Real or an in-process implementation with the same delivery semantics
Your cache Real, since eviction and serialisation are what you are testing
Third-party APIs Simulated locally, with a separate contract test against the real one
Clock, randomness, identifiers Controlled

Run the real migrations rather than loading a schema dump. Migration failures are among the most common deployment incidents, and running them in every integration suite tests them continuously for free.

Keeping the suite fast

  • Start dependencies once per suite, not once per test.
  • Parallelise with isolated data, which is the largest available speedup.
  • Seed the minimum. Large shared fixtures make tests slow and couple them together.
  • Do not re-test business rules already covered by unit tests. This level exists for the seams: mapping, transactions, serialisation, queries, wiring.
  • Budget the runtime. If the integration suite outgrows the pull request window, it gets moved to nightly, and nightly feedback is worth a fraction of per-commit feedback.

Check Your Understanding

#

Why run the real database migrations in the integration suite rather than loading a schema dump?

#

Two integration tests pass individually and fail when run in parallel. What is the most likely cause?