tldr: Unit tests check one function in isolation with dependencies mocked. Integration tests check that real components work together. Units are fast and cheap, so you write many. Integration tests are slower but catch the wiring bugs mocks hide. A healthy suite needs both, in that order of quantity.
The difference, side by side
| Unit testing | Integration testing | |
|---|---|---|
| Scope | One function, method, or class | Two or more real components |
| Dependencies | Mocked or stubbed | Real: databases, APIs, queues |
| Type | Typically white-box: you know the code | Often black-box at the interface: you know the contract |
| Speed | Milliseconds | Seconds |
| Typical count | Hundreds to thousands | Dozens to hundreds |
| Written by | The developer, with the code | Developers, sometimes QA |
| A failure means | This logic is wrong | These pieces do not connect correctly |
Two notes the classic comparison tables get wrong. The white-box and black-box labels are typical, not a law: sociable unit tests exercise real collaborators, and plenty of integration tests inspect internals. And developers write both. Integration tests are not a hand-off to a later QA phase; they live in the same repository and run in the same CI as the unit tests.
What unit tests catch
A unit test pins down logic. Given this input, the function returns that output. Discount math, date parsing, permission checks, edge cases in a validator. Because everything around the unit is mocked, a failure has exactly one suspect, and a full run finishes fast enough to execute on every save.
The mocking is the strength and the blind spot at once. The test passes as long as the code honors the contract the mock encodes. Nothing checks that the contract matches reality.

What integration tests catch
Integration tests exist for the gap mocks leave. The classics:
- The ORM generates a query the real database rejects
- Service A sends
userId, service B expectsuser_id - An API changed its error format and the mock never heard
- Transactions, timeouts, and connection pools behaving unlike any stub
Each of these passes unit testing everywhere and fails the moment two real components meet. The middle layer of the testing pyramid exists for exactly this class of bug.
A concrete failure each level misses
A checkout service computes tax and sends the payment provider an amount in dollars, but the provider expects cents. And in the reverse direction: a rounding bug charges 9.98 instead of 9.99 on one specific basket.
| Bug | What unit tests report | What integration tests report |
|---|---|---|
| Amount sent in dollars, provider expects cents | All green: the math is right and the request matches its mock | The provider's sandbox rejects the first charge, 100 times too small |
| 9.98 charged instead of 9.99 on one basket | A boundary sweep of the tax function catches it in milliseconds | One standard-basket test sails through |
Neither level substitutes for the other. The wiring bug is invisible to mocks; the logic bug hides from any single realistic path.
How to split your effort
Write unit tests with the code, as a habit rather than a phase. Add integration tests at every seam where your system meets something real: each external API, the database layer, queues and jobs. Then reserve a thin top layer of end-to-end tests for full user journeys, since components can integrate pairwise and the assembled product can still fail a real signup. The levels of testing entry maps the full stack, and end-to-end testing vs integration testing continues this comparison one level up.
FAQs
Which should be written first, unit or integration tests?
Unit tests, because they exist while the code is being written and shape its design. Integration tests follow once there are two real pieces to connect.
Can integration tests replace unit tests?
No. A suite of only integration tests runs slowly, fails ambiguously, and skips logic edge cases that are trivial to sweep at the unit level. Thin both directions fail; the ratio is the point.
Is API testing unit or integration testing?
Testing an endpoint against a running service with a real database is integration testing. See API functional testing for how teams structure that layer.
What about sociable versus solitary unit tests?
Sociable unit tests let a class use its real collaborators in memory; solitary tests mock everything. Jay Fields coined the pair, and Martin Fowler's bliki spread it. Sociable tests blur toward integration testing, and the label matters less than knowing which contracts remain unverified.
