Skip to content
ReactField

TopicsIntermediate

Automated Testing in React Apps

Vitest and React Testing Library as the React default, with Playwright for critical E2E, plus strategy paths and smell matching for brittle suites.

Mohamed ElshahawyPublished Apr 2026Updated Oct 2026
7 min read

What you'll learn

  • Paths for greenfield Vitest + RTL, critical E2E, Jest migration and coverage with judgment
  • Match smells like brittle tests or over-mocking to a fix
  • Pyramid: many fast unit/integration tests, few Playwright E2E paths
  • What to test in React apps, and what to leave alone

Automated tests are code that checks other code behaves as expected. In React apps, the 2026 default stack is Vitest (opens in a new tab) plus React Testing Library (opens in a new tab) for unit and integration tests, and Playwright (opens in a new tab) for a small set of critical end-to-end paths. Jest remains a solid migration path if you already have a green suite.

Use the strategy picker when you know your situation. Use the smell matcher when the suite already hurts. For accessible names that make role queries easier, see Accessibility & Semantics. For CI gates, see Project Standards.

A robotic arm clicks a button in a browser window while a magnifying glass inspects it and three test results show green checks
Automated tests click through your app the way a user would and report back every time the code changes.

How to use this page

  1. Pick a strategy path if you are starting or migrating.
  2. Match a smell if the suite is brittle, flaky or theatrical.
  3. Prefer Vitest + Testing Library; keep Playwright for money paths.
  4. Test what users notice, not implementation details.

Pick a test strategy

Five short paths from a test strategy hub toward Vitest, E2E, Jest migrate and coverage goals
Start from your situation, then apply one testing move.

Try it: test strategy

Pick your situation. You get an ordered approach, why it fits, and a first-week plan.

Goal

Your path

Vitest plus Testing Library is the 2026 default

Vite-native unit and integration tests with queries that match how users find UI beat Enzyme-style implementation checks.

  1. Vitest

    Runner for unit and integration tests.

  2. React Testing Library

    Query by role and text, not internals.

  3. MSW

    Mock HTTP at the network boundary.

  4. Guiding Principles (Testing Library)

    Testing Library guiding principles.

First week

Add Vitest and Testing Library. Write three tests: one pure util, one component with userEvent, one page with MSW.

Tip: many fast tests near the base of the pyramid, few slow E2E tests at the tip.

Checkpoint

You are starting a new Vite React app. What is the best default unit and integration runner?

Match a test smell

Smell chips connected to fix cards for role queries, userEvent, MSW, critical paths and Playwright waits
Name the smell, then pick one primary fix.

Try it: match a test smell

Choose the smell you see in the suite. The matcher points to a primary fix.

Smell

The smell

Tests break when you rename a prop or reorder DOM that users never notice.

Start here

Guiding Principles (Testing Library)

Query the way users find UI: roles, labels and text. Avoid enzyme-style .find() and prop peeking.

Also consider

React Testing Library

React Testing Library queries and Testing Playground.

Related: Accessible names make role queries easier

Tip: if a refactor of behavior-preserving code breaks a test, the test was probably too brittle.

Key takeaway

If a behavior-preserving refactor breaks a test, the test was probably too brittle.

Why automated testing

Split the argument if you need buy-in from non-technical stakeholders.

The three types of tests

  1. Unit - smallest pieces (utils, pure components) behave as expected.
  2. Integration - several units work together (page + form + mocked API).
  3. End-to-end (E2E) - a real browser walks a full user path.
A testing pyramid with many unit tests at the wide base, fewer integration tests in the middle and a small end-to-end tip
Write many fast unit and integration tests. Keep a handful of E2E tests for critical paths.

Getting started (Vitest + RTL)

  1. Unit and integration runner: prefer Vitest (opens in a new tab). Keep Jest (opens in a new tab) if the suite is already green and migration is not urgent.
  2. Component queries: React Testing Library (opens in a new tab). Do not start new suites on Enzyme. Its APIs push tests toward internals.
  3. HTTP mocks: MSW (opens in a new tab) at the network boundary.
  4. E2E: Playwright (opens in a new tab) by default. Cypress (opens in a new tab) is fine if the team already lives there.
  5. Watch a short walkthrough if you have never written a React unit test, then rewrite the example against Vitest + Testing Library patterns.
Terminal
# typical Vite + Vitest install shape (check current Vitest docs for flags)
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
TSX
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { expect, test } from 'vitest'
import { Counter } from './Counter'

test('increments when the user clicks', async () => {
  const user = userEvent.setup()
  render(<Counter />)
  await user.click(screen.getByRole('button', { name: /increment/i }))
  expect(screen.getByText('1')).toBeInTheDocument()
})

Checkpoint

Which question best decides whether something belongs in a test?

What to test in a React app

Most of what you control falls into visual checks and behavioral checks. Prefer behavior.

Deep dive: behavioral testing

FAQs

Tips

Code tips
// Mock browser APIs you do not import
beforeAll(() => {
  window.scrollTo = vi.fn()
})

beforeEach(() => {
  vi.mocked(window.scrollTo).mockClear()
})

// Module mocks when MSW is not the right tool
import { someFunction } from 'third-party-npm-dependency'

vi.mock('third-party-npm-dependency')

test('uses a mock implementation', () => {
  vi.mocked(someFunction).mockImplementation(() => {})
})

Watch Testing Library: everybody uses it, but nobody understands it (opens in a new tab) by Matan Borenkraout for query depth.

Testing checklist

  • New apps use Vitest + React Testing Library by default
  • Enzyme is not on the recommended path
  • Critical money paths have Playwright (or existing Cypress) coverage
  • HTTP is mocked at the boundary (MSW), not by mocking every module
  • Queries prefer role, label and text over DOM structure
  • Coverage goals do not replace critical-path tests
  • CI runs the fast suite on every PR; E2E on the right jobs

Further reading

Conclusion

Ship confidence, not theater. Vitest and Testing Library cover most React UI behavior. Playwright protects the few paths that must not break. Drop Enzyme habits, mock at the edges and ask whether a user would notice.

The key takeaway

Default to Vitest + React Testing Library, reserve Playwright for critical paths and treat coverage as a signal rather than a trophy.

Check your understanding

Test yourself6 questionsQuestion 1 of 6

What is the recommended default for unit and integration tests in a new Vite React app?

Decision framework

GoalPrefer
New React appVitest + React Testing Library
Money-path confidenceA few Playwright flows
Legacy Jest suiteKeep green; migrate package by package
Coverage vanityDrop; cover critical user paths instead

When not to add E2E

  • The bug is a pure function or a single component contract
  • The suite is already slow and flaky: fix smells first

Checklist

  • Tests assert user-visible behavior
  • Network mocked at the boundary (for example MSW)
  • Playwright reserved for critical paths

Recap

What to remember from this article

Sections read

0

Questions

0

none answered yet

Quiz

0

not taken yet

Up next: Next.js SEO Checklist · 2 min

Continue