Integration and end-to-end testing both look at more than one piece of code at once, which is why they get confused. The split is about scope. Integration testing checks that modules, components, or services talk to each other correctly: the interfaces, the contracts, the data handed from one unit to the next. End-to-end (E2E) testing validates a complete user journey through the fully assembled system, from the user's entry point all the way through, with the real dependencies in place.
The core difference#
An integration test asks: do these parts connect correctly? It wires two or more units together and checks the seam between them, often with the slower or external pieces stubbed or mocked so the test stays fast and focused. The question is about the handoff, not the whole product.
An end-to-end test asks: does the real flow work for a user? It drives the fully assembled system the way a person would, UI through backend to database and third-party services, all live. The question is about the journey, start to finish, not any single connection inside it.
For deeper treatments of each, see the end-to-end testing guide and, for the cross-system flavor of integration, system integration testing (SIT).
Side by side#
| Dimension | Integration testing | End-to-end testing |
|---|---|---|
| Scope | Between components and their interfaces | The whole flow, from the user's entry point |
| Verifies | Contracts and data handed between units | A complete user journey works |
| Dependencies | Externals often stubbed or mocked | Real stack, real services, live |
| Speed | Faster | Slower |
| Count | Many | Few |
| Pyramid level | Middle | Top |
| Fails when | A seam between parts is wrong | The assembled journey breaks for the user |
Where integration testing fits#
Integration testing sits in the middle of the testing pyramid: more numerous than E2E, cheaper to run, narrower in scope. Use it to catch the bugs that unit tests cannot see because they live in the gap between units: a service returning a field in the wrong shape, a repository mishandling the ORM, a queue consumer misreading a message. Because externals can be stubbed, these tests stay fast enough to run on every commit, which is what lets you keep a lot of them.
Where end-to-end testing fits#
End-to-end testing sits at the top: the fewest tests, the slowest to run, the widest scope. Reserve it for the critical journeys where failure costs customers or revenue, such as authentication, signup, and checkout. An E2E test exercises the real stack, so it catches failures that only appear when every piece runs together, the ones no isolated test can reach. The cost is speed and fragility, which is exactly why you keep the count small.
When the line blurs#
The boundary is not always clean, and that is fine. The same flow can read as either test depending on how much of the real system it touches.
A test that drives the checkout UI but points at mocked payment and inventory services is really an integration test wearing a browser. A test that hits two live microservices over the network, with no user interface involved, is integration testing even though it spans systems. What decides the label is not the tool, it is the scope and how real the dependencies are.
The practical guide is the pyramid. Push each check as low as it can reasonably go. If a contract between two services can be proven without standing up the whole stack, that is an integration test and it belongs lower, where it runs fast and often. Promote a check to E2E only when the thing you need to prove is the assembled journey itself, end to end, through the real system.
Which should you write?#
- Write an integration test when the risk is a broken interface or contract between parts, and you can stub the rest.
- Write an integration test when the check needs to run on every commit and a stubbed dependency keeps it fast.
- Write an end-to-end test when the thing that must work is a complete user journey through the live system.
- Write an end-to-end test for the few flows where failure costs customers or revenue, and accept that you will keep the count small.
- When a flow can be proven at both levels, prefer the lower one. Reserve E2E for what only E2E can catch.
The short version#
Integration testing proves the parts connect; end-to-end testing proves the product works for a user. One sits lower in the pyramid, cheap and numerous; the other sits at the top, slow and few. Write integration tests freely and E2E tests sparingly, and let the scope of what you need to prove decide which is which.
End-to-end tests are the expensive ones to keep green, because they depend on the live UI staying stable. ObserveOne's Autopilot generates Playwright E2E suites from a URL and self-heals selectors when the app changes, so your critical journeys keep passing through redesigns instead of breaking on every refactor.