Unit, integration, and end-to-end tests verify the same app at three different scopes. Unit tests check one piece in isolation, integration tests check pieces working together, and end-to-end tests check the whole app in a browser. This guide compares unit vs integration vs end-to-end tests for React.
The three levels
The difference is scope and speed. Higher levels cover more of the app but run slower and fail less precisely.
| Level | What it verifies | Typical tool | Speed |
|---|---|---|---|
| Unit | One component or hook in isolation | Vitest and Testing Library | Fastest |
| Integration | Components, data, and mocks together | Vitest, Testing Library, MSW | Fast |
| End-to-end | The whole app in a browser | Playwright or Cypress | Slowest |
The shape of a healthy suite is a pyramid. Many unit tests sit at the bottom, fewer integration tests in the middle, and a handful of end-to-end tests at the top.
The diagram reads top to bottom. Few end-to-end tests guard the core journeys, more integration tests guard the seams between parts, and many unit tests guard the details that are cheapest to cover.
Unit tests
A unit test renders one component or hook and checks its output, with collaborators stubbed. It runs in milliseconds and names the exact piece that broke.
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "./Counter";
test("increments the count when clicked", async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole("button"));
expect(screen.getByRole("button")).toHaveTextContent("1");
});This test isolates the counter from the rest of the app. When it fails, the counter is the only suspect. The full workflow for these tests lives in how to test React components with Vitest.
Integration tests
An integration test runs several real pieces together while stubbing only the slow or external parts. A form test that fills a field, submits, and checks a validation message is an integration test, because the component, its state, and the event system work together.
Network integration follows the same idea. The component performs a real fetch, and MSW supplies the response, so the component and the request layer are tested as one. See how to mock API requests with MSW.
End-to-end tests
An end-to-end test launches the real app in a real browser and drives it like a user. Playwright and Cypress are the common tools.
// e2e/checkout.spec.js
import { test, expect } from "@playwright/test";
test("adds an item to the cart", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Add to cart" }).click();
await expect(page.getByText("1 item")).toBeVisible();
});This test proves the whole journey works, from the page load through the click to the updated cart. It is also slower and more flaky than the lower levels, so it covers only the highest-value flows.
How many of each to write
Write many unit tests because they are fast and precise. Write fewer integration tests around the boundaries where pieces meet. Write very few end-to-end tests, limited to the journeys that define your product.
This balance keeps the suite fast while still catching the failures that matter most, and it makes every red test easier to act on.
When a high-level test catches a bug, push the failure down. Rewrite the failing case as a lower-level test where possible, so the suite stays fast and the next failure is easier to debug.
Choosing the right level
- Testing one pure function or hook: unit test.
- Testing a component with its provider or router: integration test.
- Testing a user journey across pages: end-to-end test.
- Testing a visual detail that needs a real browser: end-to-end test.
Do not duplicate the same case at every level. Cover it at the lowest level that can reproduce it, then move up only when a higher level adds real confidence.
What to learn next
The pitfalls that cross all three levels are collected in common React testing mistakes.
Rune AI
Key Insights
- Unit tests verify one component or hook in isolation.
- Integration tests verify components, data, and mocks working together.
- End-to-end tests drive the full app in a real browser.
- Write many unit tests, fewer integration tests, and few end-to-end tests.
- Push a failing high-level test down to the lowest level that reproduces it.
Frequently Asked Questions
Which test level should I write the most of?
Do end-to-end tests make unit tests unnecessary?
Conclusion
Unit tests check one piece, integration tests check pieces together, and end-to-end tests check the whole app. Push tests down the pyramid for speed and precision.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.