A unit test isolates a small piece with its dependencies replaced, so it runs fast and pinpoints failures. An integration test exercises several real components together, so it catches the wiring and contract bugs that unit tests miss. Both are needed.
Before you start
You should be comfortable with mocks and test structure. This article covers the two levels and how to balance them.
Step-by-step walkthrough
Step 1: Unit tests for logic and edge cases
A unit test replaces dependencies with fakes or stubs and asserts the unit’s behavior, which is fast and deterministic. Use them to cover branches, boundary values and error handling in a function, where a precise failure message helps.
Step 2: Integration tests for wiring and contracts
An integration test uses real components: a real database, a real HTTP client pointed at a fake server, or several modules together. It catches serialization mismatches, SQL errors and misconfigured wiring that isolated tests cannot see.
Step 3: Balance the cost and confidence
Unit tests are cheap and many, integration tests are slower and fewer, so a suite leans on units with a focused set of integration tests around the risky boundaries. The exact shape depends on the system, but all-mocked suites and all-integration suites both have failure modes.
Worked scenario
The unit test isolates logic; the integration test proves the query.
test('discount applies above threshold', () => {
expect(discount(120)).toBe(12); // pure unit
});
test('order repository saves and reads back', async () => {
await repo.save(order);
expect((await repo.findById(order.id)).total).toBe(order.total); // real DB
});Walk through the example
The first test checks the discount rule in isolation, fast and precise. The second uses a real database to prove the mapping and query work, which no mock would catch. Together they cover the rule and the wiring.
Common mistake
Mocking everything, so the suite passes while the real serialization or query is broken. The opposite is making every test an integration test, so the suite is slow and a failure does not say which layer broke.
Verify the behavior
Break a query or a serialization mapping and confirm an integration test fails while unit tests pass, proving the layer they cover. Change a business rule and confirm the unit test fails fast. Time both levels to keep the suite practical.
Interview exercise
Why can a fully mocked suite pass while the app is broken?
Answer and reasoning
Because mocks encode assumptions about dependencies rather than their real behavior. If the real service changes its response shape or the database rejects a query, the mock still returns the expected value, so the test stays green while production fails. Integration tests exercise the real contract and catch that.
Continue learning
Compare isolation in Mocks, stubs and fakes and contracts in Contract testing. Read the Martin Fowler on the test pyramid and try the Testing interview questions.