Levels of Testing
Unit, integration, contract, component and end-to-end testing — what each level can prove, what it costs to run, and how to decide which one a given risk belongs at.
10 articles · updated 19 September 2026
Every automated test answers a question, and the useful way to sort tests is by the size of the question they answer. A unit test asks does this function do what it says. An end-to-end test asks can a person actually buy something. Both are worth having. They are not substitutes, and the most expensive mistake in test automation is treating them as if they were.
#The trade every level makes
Move up a level and your test covers more, in exchange for three things:
- Speed. A unit test runs in microseconds. An end-to-end test runs in seconds, and a suite of them in tens of minutes.
- Determinism. Every real component you include is a component that can be slow, unavailable, or in a state you did not arrange.
- Diagnosis. A failing unit test names the function. A failing end-to-end test tells you that checkout is broken, and then you go and find out why.
Move down a level and you buy all three back, in exchange for the only thing that actually matters to a user: the confidence that the pieces work together. Nothing at the unit level can tell you that the ORM emits the query you think it does, that two services agree on a field name, or that the button is reachable.
#The levels, in order
- Unit testing — one unit, no I/O
- Unit testing with mocks — the solitarist position, and where it goes wrong
- Unit testing without mocks — the sociable position, and where it goes wrong
- Test doubles — mock, stub, fake, spy and dummy, used precisely
- Integration testing — your code against a real dependency
- Integration testing with stubs — your code against a dependency you control
- Contract testing — proving two services still agree without deploying both
- Component testing — one deployable, in isolation, through its real interface
- End-to-end testing — the whole system, driven the way a user drives it
- The testing pyramid — how many of each
#How to decide where something belongs
Ask what would have to be wrong for this test to fail, and whether that is
the smallest thing that could be wrong. A test that fails because a discount
was calculated incorrectly belongs at the unit level. A test that fails
because the discount column is DECIMAL(4,2) and the number does not fit
belongs at the integration level. A test that fails because the discount is
correct everywhere but never rendered belongs at the end.
Most teams that feel their suite is unreliable have tests sitting one or two levels higher than the risk they were written for. The fix is almost never a better tool; see the testing pyramid and flaky tests.
Everything in Levels of Testing
The Testing Pyramid
foundationWhy cheap tests must outnumber expensive ones, what the pyramid actually claims, the trophy and honeycomb alternatives, and how to tell which shape you really have.
Unit Testing
foundationWhat a unit actually is, what belongs in a unit test and what does not, and the properties that separate a unit suite people run from one they skip.
Unit Testing with Mocks
practicalThe solitarist approach — isolate the unit behind test doubles. What it buys, the coupling it creates, and the three rules that keep a mock-heavy suite maintainable.
Unit Testing without Mocks
practicalThe sociable approach — real collaborators, state-based assertions, and tests that survive refactoring. What it buys, where it breaks down, and how to keep it fast.
Test Doubles
foundationDummy, stub, spy, mock and fake — Meszaros's five kinds of test double, what each is for, and why using the words precisely makes code reviews shorter.
Integration Testing
foundationTesting your code against real dependencies — databases, HTTP clients, message brokers — with Testcontainers, and what belongs at this level rather than above or below it.
Integration Testing with Stubs
practicalTesting against a dependency you control — WireMock, MSW and in-process servers — so error paths, timeouts and rate limits become testable instead of theoretical.
Contract Testing
advancedProving two services still agree without deploying both — consumer-driven contracts with Pact, provider verification in CI, and where contract testing beats end-to-end.
Component Testing
practicalTesting one deployable in isolation through its real interface, with its own dependencies containerised and everything beyond its boundary stubbed.
End-to-End Testing
foundationWhat belongs in an end-to-end suite and what does not, how many journeys are enough, and the practices that keep a browser suite from becoming the thing everyone ignores.