Unit vs Integration vs E2E Tests: What's the Difference
Basics4 minInnovialabs editorial team
In short
A unit test checks one function in isolation, an integration test checks parts working together, and an E2E test runs a full user journey; pick the mix based on where your project actually breaks.
The difference in short
The three types differ in scope, meaning how much of the system they exercise at once:
| Type | What it checks | Speed | Stability | What it catches |
|---|---|---|---|---|
| Unit | one function or class in isolation | very fast | high | logic errors and edge cases |
| Integration | modules together: code + database, service + API | medium | medium | bugs at the seams, wrong queries, broken contracts |
| E2E | a user scenario through the UI | slow | lower | whole user journeys that are broken |
The higher the level, the more confidence one test gives and the more it costs to write, run and maintain.
Unit tests
A unit test takes a small piece of code and checks it without external dependencies. The database, network and clock are replaced with mocks or stubs.
// calcDiscount.test.js
import { calcDiscount } from "./calcDiscount";
test("discount never exceeds the price", () => {
expect(calcDiscount(100, 150)).toBe(100);
});
Good for: calculations, validation, formatting, business rules with many conditions.
Weak spot: every unit test can be green while the app is broken, because the modules are wired together incorrectly.
Integration tests
An integration test checks that several parts work together. For example: an HTTP request to your API reaches the handler, data is saved to a real test database, and the response has the right shape.
Typical examples:
- an API endpoint plus a test database;
- a UI component together with its children and state;
- a service plus a message queue;
- your code plus an external API in sandbox mode, or mocked at the network level.
Good for: verifying contracts, SQL queries, serialization, access rules.
E2E tests
An end-to-end test drives the app like a user: opens a browser or mobile emulator, clicks buttons, fills in forms and checks the result. Playwright and Cypress are popular choices for the web.
Good for: critical paths such as sign-up, login, checkout and payment in test mode.
Weak spot: slow and sensitive to the environment. Without careful handling of timeouts, animations and test data you get flaky tests that fail every other run, and the team stops trusting them.
Pyramid vs trophy
The testing pyramid (Mike Cohn's idea): many unit tests at the base, fewer integration tests in the middle, a few E2E tests on top. The logic is that fast, cheap tests should form the foundation.
The testing trophy (Kent C. Dodds' model): static analysis (types, linter) at the bottom, then some unit tests, with integration tests as the widest part, and a few E2E on top. The argument is that integration tests are closer to real usage and give more confidence per unit of effort, and modern tooling has made them fast enough.
Neither model is about exact percentages. Both point the same way: many cheap checks, few expensive ones.
How to choose the mix for your project
Look at where your bugs actually live:
- Complex business logic (calculations, pricing, rules): more unit tests.
- Many integrations (APIs, database, payments, CRM): focus on integration tests.
- UI with typical CRUD screens: component integration tests plus a few E2E tests on key flows.
- Money or critical data involved: E2E on those paths is a must.
A practical start: take your 3 to 5 most important user scenarios and cover them with E2E. Then add an integration test for every bug found in production, and unit tests for complex logic.
Common mistakes
- Testing implementation details instead of behavior, so tests break on every refactor.
- Mocking everything, so the test checks the mocks rather than the system.
- Trying to cover every logic branch with E2E, which makes the suite slow and fragile.
- Chasing a coverage percentage instead of protecting important scenarios.
- Not running tests in CI on every pull request.