Testing React Applications
Testing React applications means verifying that components behave correctly from the user's perspective: the right content appears, interactions produce the right results, errors are handled, and the UI stays accessible. The dominant approach is React Testing Library (RTL), which renders components into a simulated DOM and encourages tests that interact with the page the way people do — by role, label, and visible text — instead of reaching into component internals.
A balanced React test suite combines fast component and integration tests (Vitest or Jest with RTL), realistic network mocking (Mock Service Worker), and a smaller set of end-to-end tests in real browsers (Playwright). The goal isn't coverage for its own sake; it's confidence that changes don't break what users rely on.
TL;DR
- Use React Testing Library with Vitest (or Jest) and a DOM environment such as jsdom.
- Query by role and accessible name first (
getByRole("button", { name: /save/i })). - Simulate interactions with
@testing-library/user-event, not raw events. - Use **
findBy*andwaitFor** for async UI; avoid arbitrary timeouts. - Mock network requests with MSW, not the
fetchfunction or your data layer. - Test behavior, not implementation; reserve Playwright for critical end-to-end journeys.
Quick Example
Testing a login form that calls an API, using Vitest, RTL, user-event, and MSW:
Core Concepts
Query Priority
Testing Library recommends querying the way users and assistive technology find elements:
If you can't find an element by role or label, that's often an accessibility bug in the component.
getBy, queryBy, findBy
getBy*— throws if not found; for elements that should be present now.queryBy*— returnsnull; for asserting absence.findBy*— returns a promise that retries until found (default 1 second); for async UI.
User Events
user-event simulates full interactions — focus, keyboard, pointer, and input events in realistic order — unlike fireEvent, which dispatches single events. Always await its calls.
Mocking the Network
Mock Service Worker intercepts requests at the network level in Node and the browser, so components, data-fetching libraries (like TanStack Query), and error handling run unchanged. It's more realistic than mocking fetch or your API module. See Mocking & Stubbing.
Testing Hooks and Context
Test custom hooks through components that use them, or with renderHook for reusable hooks. Wrap components in the providers they need (router, query client, theme) with a custom render helper.
Choosing Test Levels
Best Practices
Test Behavior Users Care About
Assert visible outcomes — text, enabled state, navigation, network requests made — not component state or which hooks were called.
Create a Custom Render
Wrap providers (router, query client, theme, i18n) once in a renderWithProviders helper so tests stay short.
Fail on Unhandled Requests
Configure MSW with onUnhandledRequest: "error" so tests don't silently hit the network or miss mocks.
Keep Tests Independent
Reset MSW handlers, mocks, and stores between tests; each test should pass in isolation and in any order.
Add Accessibility Checks
Use jest-axe or vitest-axe on key components to catch missing labels and roles automatically.
Keep End-to-End Tests Few and Focused
Cover signup, checkout, and other money paths end to end; test edge cases at the component level where it's faster.
Common Mistakes
Testing Implementation Details
Asserting internal state or calling component methods breaks on every refactor even when behavior is unchanged.
Using getByTestId for Everything
Test IDs don't verify that users (or screen readers) can find the element.
Arbitrary Waits
await new Promise(r => setTimeout(r, 500)) makes tests slow and flaky. Use findBy* or waitFor.
Snapshot Overuse
Huge snapshots get approved without review and fail on trivial markup changes. Snapshot small, stable outputs only.
Mocking Too Much
Mocking child components, hooks, and API modules can leave tests that pass while the real app is broken.
FAQ
What is the best way to test React components?
Render them with React Testing Library, interact through user-event, query by role and label, mock network requests with MSW, and assert what the user would see.
Should I use Jest or Vitest?
Vitest is faster and integrates naturally with Vite-based projects; Jest remains common in existing codebases and some frameworks. Both work with React Testing Library.
How do I test components that fetch data?
Mock the API with MSW, render the component inside any required providers, and use findBy* queries to wait for loaded content and error states.
Do I still need end-to-end tests?
Yes, for critical flows that span routing, authentication, the real backend, and multiple browsers. Keep them few and rely on component tests for breadth.
How do I test React Server Components?
Test server logic and data functions directly, test client components with RTL, and cover the integrated page with end-to-end tests, since RSC rendering depends on the framework.
Related Topics
- Vitest — Fast test runner for Vite projects
- Jest — The long-standing JavaScript test runner
- Playwright — End-to-end testing in real browsers
- Mocking & Stubbing — Test doubles and network mocks
- Accessibility — Semantic queries double as a11y checks