Integration testing is software testing concerned with verifying that multiple software components work correctly when combined. Whereas unit testing concentrates on an individual unit in relative isolation, integration testing deliberately examines relationships and interfaces between units, services, modules, databases, external systems, message brokers, APIs, or other collaborating components. The integration boundary can be very small, such as two classes communicating through an interface, or very large, such as an application communicating with a database and third-party service. The defining concern is interaction: the test seeks to discover faults that arise when individually developed components exchange data, invoke one another, share state, or depend on common infrastructure.
Integration defects frequently occur because components make different assumptions about one another. One component may produce a value in a format another component does not understand, an API may require a field that the caller omits, a database query may expect a schema different from the deployed schema, or a service may return an error that the consuming application handles incorrectly. Authentication and authorisation boundaries can also introduce integration defects when credentials, tokens, permissions, or identity formats are inconsistent. Timing, retries, transaction boundaries, concurrency, character encoding, serialisation, network protocols, and configuration can create additional failure modes. Integration tests therefore examine contracts and communication mechanisms rather than merely checking the internal correctness of individual components.
Several integration-testing strategies exist. In a bottom-up approach, lower-level components are integrated and tested before higher-level components are added. In a top-down approach, higher-level components are tested using stubs or substitutes for lower-level dependencies that are not yet available. Big-bang integration combines many components before testing them, but failures can then be difficult to localise. Incremental integration generally provides better diagnostic information because interfaces are introduced and verified progressively. Modern systems may also use contract testing, in which a consumer and provider agree on an explicit interaction contract and tests verify that each side satisfies the required assumptions. Contract tests can be particularly valuable for independently deployed microservices.
Integration tests can use real dependencies or controlled substitutes. For example, an application repository can be tested against a temporary real database to verify SQL queries, transactions, constraints, and schema interactions. An HTTP integration test can exercise a real application server while replacing an unrelated payment provider with a test service. Containers and ephemeral infrastructure have made it increasingly practical to create disposable databases, queues, caches, and service environments during automated testing. The choice between real and simulated dependencies involves trade-offs: real dependencies provide stronger environmental realism, while substitutes can improve speed, determinism, isolation, and fault control. The appropriate strategy depends on what behaviour the test is intended to establish.
Integration testing occupies an important position between unit-level and system-level testing. A unit test might establish that a database repository method constructs correct data, while an integration test establishes that the repository communicates correctly with an actual database. A system test can then establish that the complete application performs a business workflow correctly across its user-facing and supporting components. These boundaries are not universal because software architectures differ. A monolithic application may have relatively simple internal integration boundaries, while a distributed architecture can have many network and service boundaries. Consequently, the term integration test describes a testing purpose and scope rather than one fixed technical procedure.
Integration tests can be slower and more operationally demanding than unit tests because they may require infrastructure, processes, network communication, databases, credentials, containers, or service configuration. They can also be susceptible to environmental differences and timing-related failures. Good integration suites therefore normally control their dependencies carefully, create known test data, isolate tests where practical, clean up state, and avoid unnecessary reliance on shared environments. Database integration tests might use transactions or disposable schemas; API tests may use dedicated test endpoints; messaging tests may use isolated queues or topics. Test environments should be sufficiently representative to detect important integration faults without making the entire development feedback loop impractically slow.
Integration testing is especially important when software has external boundaries. REST and GraphQL APIs, SQL databases, object stores, authentication systems, payment gateways, cloud services, message queues, event streams, operating-system interfaces, and hardware devices all introduce interactions that cannot necessarily be validated through isolated unit tests. Integration testing can also reveal configuration errors such as incorrect connection strings, incompatible protocol versions, missing permissions, and incorrect environment variables. For regulated or safety-critical systems, the evidence produced by integration testing may form part of formal verification and validation activities, although the exact requirements depend on the domain and applicable standards.
A common misconception is that integration testing simply means running the entire application. Full-system execution can constitute a system or end-to-end test depending on its scope and objective. Integration testing is specifically concerned with the correctness of interactions among integrated components. Another misconception is that integration tests make unit tests unnecessary; in practice, the two provide different kinds of feedback. Unit tests can identify local logic errors quickly, while integration tests expose interface and environmental problems. A robust testing strategy generally uses both. Integration testing is also closely related to continuous integration, because automated integration tests can run whenever changes are merged, providing rapid evidence that newly combined components remain compatible.