Unit vs Integration vs End-to-End Tests for React

Understand the three testing levels for React, what each one verifies, and how to balance speed against confidence.

7 min read

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.

LevelWhat it verifiesTypical toolSpeed
UnitOne component or hook in isolationVitest and Testing LibraryFastest
IntegrationComponents, data, and mocks togetherVitest, Testing Library, MSWFast
End-to-endThe whole app in a browserPlaywright or CypressSlowest

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.

Testing pyramid

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.

App.jsxApp.jsx
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.

index.jsindex.js
// 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Which test level should I write the most of?

Unit and integration tests. They run fast and pinpoint failures. Write few end-to-end tests, limited to the core user journeys.

Do end-to-end tests make unit tests unnecessary?

No. End-to-end tests are slow and flaky, so they cannot cover every edge case. Unit and integration tests catch most bugs before a browser is involved.

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.