All topics
Testingintermediate

Integration Testing vs Unit Testing

The distinction between testing units in isolation with mocked dependencies versus testing how multiple real components work together, and the tradeoffs of each.

Unit tests and integration tests sit at different points along a spectrum trading off isolation and speed against realism and confidence, and understanding when each is the right tool — rather than treating one as simply 'better' than the other — is a genuinely important architectural judgment call that interviewers use to gauge testing maturity beyond just syntax knowledge.

Unit testing is like a car manufacturer bench-testing each individual part (the engine, the brakes, the transmission) separately on isolated test rigs to confirm each one works correctly on its own. Integration testing is actually bolting several of those real parts together into a partially assembled vehicle and driving it around a test track, confirming the engine's real power actually reaches the real wheels correctly through the real transmission, not just that each part individually performed fine in isolation.

Key Concepts

1
A unit test isolates a single function, class, or module, typically mocking or stubbing out every external dependency (as covered under mocking), verifying that one specific piece of logic behaves correctly on its own, independent of how its real dependencies actually behave. This isolation makes unit tests fast (no real network calls, database queries, or file I/O) and precise about pinpointing exactly which piece of logic broke when a test fails, but it also means a fully-passing unit test suite says nothing at all about whether the *real*, non-mocked versions of those dependencies actually integrate correctly together — a classic gap where each piece works perfectly in isolation, but the assembled whole is subtly broken (a mismatched API contract between two real services, for instance, that both sides' mocks silently agreed to but the real implementations don't actually honor).
2
An integration test instead exercises multiple real components together — hitting an actual (test) database, calling a real internal API endpoint, or rendering a real component tree without mocking child components — verifying that the pieces genuinely work correctly *together*, not just independently. This gives higher confidence that the assembled system actually functions as intended, at the real cost of being slower to run, harder to set up (often requiring test databases, test servers, or other real infrastructure), and, when a test does fail, less immediately precise about pinpointing exactly which specific piece among several real, interacting components is actually at fault.
3
The commonly cited mental model for how to balance the two across a healthy test suite is the 'testing pyramid': many fast, cheap, precise unit tests forming a wide base, a smaller number of integration tests verifying that key pieces genuinely connect correctly, and a still smaller number of full end-to-end tests (covered separately) simulating real user flows through the entire, fully-assembled system — the pyramid shape reflecting that you generally want the bulk of your confidence coming from the fast, cheap layer, with progressively fewer, more expensive tests as you move toward full-system realism, rather than either extreme (all unit tests with nothing verifying real integration, or all slow end-to-end tests with no fast feedback loop at all).