Integration test

On this page 7

An integration test is an automated test that verifies how two or more components behave together, for example application code with a real database, a message broker, a file system or another service. In software testing, it sits between the unit test, which isolates one small piece of code, and the end-to-end test, which exercises the whole system through its user interface or public entry points.

Integration tests catch the defects that live at the boundaries: wrong SQL, mismatched data formats, incorrect configuration, serialization errors and misunderstood API contracts. These failures are invisible to unit tests that replace every collaborator with a mock.

How an integration test works

An integration test follows the familiar arrange, act, assert structure, but the arrange step prepares real infrastructure instead of fakes. It starts or connects to a database, loads a known data set, and configures the components under test as they would be configured in production.

The act step calls the integrated code through a meaningful entry point, such as a repository method, a service function or an HTTP endpoint of the application. The assert step then checks observable results across the boundary: rows written to the database, messages published to a queue, or the exact JSON returned by the endpoint. Finally, a teardown step resets state so that tests remain independent.

The following table lists the typical boundaries that integration tests cover and what each detects.

BoundaryWhat is exercisedDefects typically found
DatabaseQueries, migrations, transactionsInvalid SQL, missing indexes, constraint violations
Message brokerPublishing and consuming eventsSerialization errors, wrong topic names
HTTP APIRouting, validation, serializationWrong status codes, broken JSON contracts
External serviceClient code against a real or simulated serviceAuthentication and timeout handling
File system or object storageReading and writing filesPath, encoding and permission problems
Typical integration boundaries and the defects they reveal

Integration tests in the test pyramid

The test pyramid recommends many fast unit tests, fewer integration tests and only a small number of end-to-end tests. Integration tests occupy the middle layer because they are slower and more complex than unit tests but far cheaper and more stable than full end-to-end runs.

AspectUnit testIntegration testEnd-to-end test
ScopeOne function or classSeveral components and real infrastructureWhole system through its interface
Typical durationMillisecondsTens of milliseconds to secondsSeconds to minutes
DependenciesReplaced by mocks or stubsReal database, broker or serviceFully deployed environment
Failure diagnosisPreciseModerateOften difficult
Flakiness riskVery lowModerateHigh
Integration tests compared with neighboring test types

Some teams prefer a testing trophy or honeycomb shape that puts integration tests at the center, especially for services whose logic is mostly data access and API wiring. Such code has little isolated logic to unit-test, so integration tests give the best confidence per test.

Approaches and scopes of integration testing

The term covers several scopes, and teams should agree on which one they mean. A narrow integration test checks a single boundary, such as one repository class against a real database. A broad integration test wires many components together and resembles a smaller end-to-end test.

Classic integration strategies describe the order in which modules are combined, which still matters in large systems and embedded software.

StrategyHow it proceedsSuitable when
Big bangCombine all modules at onceVery small systems only
Top-downStart from high-level modules, stub lower onesEarly validation of main workflows
Bottom-upStart from low-level modules, drive them with test harnessesInfrastructure and libraries mature first
Sandwich or hybridCombine top-down and bottom-upLarge layered systems
Contract testingVerify each side against a shared contractMicroservices owned by different teams
Integration strategies and when they apply

Building reliable integration tests

The biggest practical challenge is providing realistic dependencies cheaply. Modern practice favors disposable containers started for the test run, for example a real PostgreSQL or Kafka instance launched through a container library, instead of a shared test server that accumulates stale data.

  • Use the same engine as production: an in-memory database with a different SQL dialect hides real defects.
  • Isolate test data: wrap each test in a rolled-back transaction or truncate tables between tests.
  • Control time and randomness: inject clocks and seeds so results are deterministic.
  • Simulate third parties at the network level: an HTTP stub server tests real client code without calling a paid external API.
  • Run in parallel safely: give each worker its own schema or container.

Integration tests also contribute to test coverage, but coverage numbers alone say little about boundary quality. A useful target is that every query, every external call and every published event type is exercised by at least one integration test.

Common problems and their causes

Poorly designed integration suites become slow and flaky, and developers start ignoring their failures. The table below links the most frequent symptoms to causes and remedies.

SymptomLikely causeRemedy
Intermittent failuresShared state or timing assumptionsIsolate data, wait on conditions instead of fixed sleeps
Suite takes too longStarting infrastructure per testStart containers once per suite, reset data per test
Passes locally, fails in CIEnvironment differencesContainerized dependencies with pinned versions
Bugs reach production anywayToo many mocks inside integration testsUse real components at the tested boundary
Hard to understand failuresTests cover too much at onceNarrow the scope to one boundary per test
Integration test problems, causes and remedies

Understanding it simply

Testing each part of a car separately, the engine on a bench and the brakes on a rig, proves that each part works alone. It does not prove that the parts work once they are bolted together.

An integration test is the check after assembly. It connects real pieces of a program, such as the code and its database, and confirms that they understand each other. Many expensive bugs hide exactly at these connection points.

Applied in practice

A team builds an order service that saves orders in PostgreSQL and publishes an event to a message broker. Its unit tests all pass because the database and broker are mocked. In production, however, orders with long customer notes fail, because a database column is limited to 255 characters and the mock never enforced that limit.

The team adds integration tests that start real PostgreSQL and broker containers at the beginning of the CI run. One test saves an order with a 1,000-character note and checks both the stored row and the published event. The test reproduces the failure, a migration widens the column, and similar boundary defects are now caught before release. The suite of 60 integration tests adds about three minutes to the pipeline, a cost the team accepts.