What Is Integration Testing? Types, Narrow vs Broad, and Tools
Integration testing checks that independently built parts work when connected, and the split that decides everything is whether the test is narrow or broad.
Gautam · 14 min read
autosana.ai

Integration testing checks that independently developed parts of a system work correctly once they are connected to each other. Unit tests prove each part is right on its own. Integration tests prove the seams between them hold.
The word causes more confusion than almost any other term in testing, and the confusion has one specific cause, which the rest of this guide is built around.
Why does nobody agree on what an integration test is?
Because the term covers two very different activities.
Martin Fowler says so directly in his bliki entry: integration tests determine whether independently developed units work correctly when connected, and the term has become blurred even by the diffuse standards of the software industry, to the point where he avoids using it. The specific blur he names is scope. Many people assume an integration test must be broad, when it can be done far more effectively with a narrow one.
That gives us the only distinction worth learning.
A narrow integration test exercises the code in your service that talks to one external part, usually against a test double standing in for it. It is small, fast, and deterministic.
A broad integration test stands up several real services together and runs a scenario across all of them. It is slow, expensive, and the thing most people picture when they hear the phrase.
Teams that confuse the two end up building the slow kind by default and then wondering why the suite takes forty minutes.
| Narrow Integration Test | Broad Integration Test | |
|---|---|---|
| What is real | Your code only | Several services |
| What stands in | A double for the other side | Nothing |
| Speed | Milliseconds to seconds | Seconds to minutes |
| Determinism | High | Depends on every service involved |
| What it proves | Your side of the contract works | The whole path works today |
Worth holding onto: most of what people want from integration testing is available from narrow tests, at a fraction of the cost.
Where did the term come from?
The history explains the confusion. Fowler traces it to the 1980s, when waterfall dominated. A design phase specified the interfaces and behaviour of each module, modules were handed to individual developers who might spend months building one in isolation, and the finished module went to QA.
Unit testing came first, checking the module against its specification. Then came integration testing, where the modules were combined, either into subsystems or into the whole system, and higher level tests ran against all of them at once.
That original meaning was unavoidably broad, because the modules genuinely had never met. Modern continuous integration removed the premise, and the word stayed.
What are the types of integration testing?
The classical taxonomy describes the order in which you connect things.
Big bang. Connect everything at once and test. Simple to schedule and brutal to debug, because a failure could come from any seam.
Top down. Start with the high level modules and stub the lower ones, replacing stubs as they arrive. You see the user-facing behaviour early and the foundations last.
Bottom up. Start with the lowest modules and build upward with drivers calling them. The foundations are solid early and nothing resembles the product until late.
Sandwich or hybrid. Both directions at once, meeting in the middle. Faster in practice and harder to coordinate.
These names still appear in every syllabus and matter less than they used to, because continuous integration means components meet daily rather than at the end of a project. The decision that actually affects your suite today is narrow against broad, not top down against bottom up.
How do doubles and contract tests fit in?
This is the part that makes narrow testing safe, and it is where most explanations stop too early.
If a narrow integration test runs against a double, the obvious objection is whether the double is faithful. Fowler's answer is that you test that separately, with contract tests. The double asserts how you believe the provider behaves, and the contract test checks that belief against the real provider on its own cadence.
Used together, you can integrate against an external service with confidence without ever running your tests against a real instance of it, which removes the slowest and least reliable part of most builds. Teams working this way may still run some end-to-end system test against real services, but only as a final smoke test across a very limited set of paths, and with a mature capability for verifying behaviour in production, some run none at all.
The trade-off gets debated honestly in practice. A r/QualityAssurance thread asking whether contract testing or integration testing alone is enough, or whether both are needed, is the short version of a long argument.
What does integration testing look like in practice?
A worked example fixes the vocabulary.
An order service calls a payment provider. Four tests exist, and only two of them are integration tests.
- Unit test. The function that builds the payment request produces the right payload from an order. Nothing leaves the process.
- Narrow integration test. The order service calls a simulated payment provider that returns a decline, and the service marks the order as failed and does not ship. Fast, deterministic, runs on every commit.
- Contract test. The simulated decline response matches what the real provider actually returns. Runs on a slower cadence against the provider.
- Broad integration test. The order service, the inventory service and the payment sandbox all run together, and an order completes end to end. Slow, run before release.
Teams that skip step 3 get fast tests that pass against a fiction. Teams that skip step 2 get a suite nobody runs.
What tools do teams use for integration testing?
The tooling splits along the same narrow and broad line.
Testcontainers gives a run throwaway, lightweight instances of databases, message brokers, browsers or anything else that runs in a Docker container. You define test dependencies as code and the containers are created and deleted around the test, so every run starts from a known state. This is how teams make narrow tests real rather than mocked, and all it requires is Docker.
WireMock Cloud simulates the environments an application depends on, with statefulness and production-realistic failures. The failure injection matters more than it sounds, because a timeout or a malformed response from a partner is exactly what a real sandbox will never produce on demand.
Shared staging environments are the tool most teams actually use and the one that causes the most trouble. A broad integration suite pointed at a shared staging environment inherits every other team's state, which is why those suites gain a reputation for flakiness that has nothing to do with the code under test. A r/devops thread on the best approach to a staging database is mostly teams working out how to stop sharing one.
Framework test runners carry the rest. Most teams never buy anything for integration testing, because the hard parts are state, isolation and speed rather than tooling.
Service virtualisation and contract testing tools sit alongside both, keeping doubles honest.
Integration testing vs unit testing vs end to end
| Unit | Integration | End to End | |
|---|---|---|---|
| Scope | One function or class | A seam between parts | The whole product |
| Dependencies | None, all mocked | Some real, some doubled | All real |
| Finds | Logic errors | Interface and contract errors | Journey and environment errors |
| Runs on | Every save | Every commit | Every build or nightly |
| Fails because | Your logic is wrong | Your assumption about the other side is wrong | Anything |
The ratio between the three is where teams go wrong in both directions. A r/ExperiencedDevs thread asking about working on a project with zero unit tests and thousands of integration tests pulled 93 votes and 135 comments, which is a fair indication of how common the imbalance is, and a separate thread on adding integration and end-to-end testing to a UI project with more than twenty microservices shows the opposite problem.
Where does integration testing stop and end-to-end begin?
The useful line is whose failure you are willing to accept.
An integration test should fail when your side of a seam is wrong. If it also fails because a third party was slow, a certificate expired or a shared environment held stale data, it has become an end-to-end test and will be treated like one, which means retried and eventually ignored.
That is also why end-to-end testing is a separate discipline rather than more integration testing. It drives the real product the way a person does, across real services and real devices, and it answers a question no integration test asks: whether the journey completes. Agent-based platforms such as Autosana work at that layer, which is above this one and not a replacement for it.
Conclusion
Integration testing is not one practice, and treating it as one is what makes suites slow.
Decide narrow or broad for each seam before you write the test. Put narrow tests against doubles on every commit, keep the doubles honest with contract tests, and reserve the broad kind for the small number of paths where seeing everything run together is genuinely worth the minutes.
Frequently asked questions
What is integration testing in simple terms?
Checking that separately built parts of a system work correctly once they are connected. Unit tests prove each part is right alone. Integration tests prove the joins hold.
What is the difference between narrow and broad integration tests?
A narrow test exercises only your code against a double standing in for the other side, so it is fast and deterministic. A broad test runs several real services together, so it is slow and depends on everything involved being healthy. Most of the value people want is available from narrow tests.
What are the four types of integration testing?
Big bang, where everything is connected at once; top down, where high level modules come first with stubs beneath; bottom up, where low level modules come first with drivers above; and sandwich or hybrid, combining both directions. The taxonomy matters less under continuous integration than the narrow against broad decision.
Is integration testing the same as end to end testing?
No. An integration test checks one seam and should fail only when your side of it is wrong. An end-to-end test drives the entire product across real services and fails for any reason at all, which is why the two need different budgets and different cadences.
Do you still need integration tests if you have contract tests?
Yes, and they do different jobs. A narrow integration test proves your code handles the response correctly. A contract test proves the response you are handling is the one the provider actually sends. Keeping only the second gives you a correct assumption and untested handling.
What tools do you need for integration testing?
Often none beyond your test runner. Where dependencies are involved, Testcontainers gives each run a real throwaway database or broker through Docker, and tools such as WireMock Cloud simulate external services including failure modes a real sandbox will not reproduce on demand.
How many integration tests should a project have?
Enough to cover each seam once per meaningful behaviour, and far fewer than the number of unit tests. A suite with thousands of integration tests and almost no unit tests is usually paying integration prices for logic coverage.
.png)