Testing React

The three layers of testing in modern React applications: Unit (logic), Component (behavior), and End-to-End (user flows).

React6 min readConcept 44 of 48

The Testing Pyramid

Testing ensures your code works and prevents regressions when you make changes. In React, we generally think about three layers:

**1. Unit Testing:** Testing pure functions in isolation (e.g., calculateTotal(items)). Fast and cheap. Usually done with **Vitest** or Jest.

**2. Component Testing:** Testing a single React component (e.g., <CheckoutForm />). We test how it behaves, not how it's written. Usually done with **React Testing Library**.

**3. End-to-End (E2E) Testing:** Spinning up a real browser and clicking through the app like a real user (e.g., adding an item to cart and checking out). Slow but highly confident. Done with **Playwright** or Cypress.

Testing Behavior, Not Implementation

Historically, developers tested React components by checking their internal state (e.g., expect(wrapper.state('isOpen')).toBe(true)).

This was fragile. If you refactored the component to use useReducer instead of useState, the test would break even if the UI worked perfectly.

**React Testing Library (RTL)** changed the paradigm. It forces you to test what the user sees: expect(screen.getByText('Hello')).toBeInTheDocument().

Writing an RTL Test

A typical component test follows the AAA pattern: Arrange, Act, Assert.

**Arrange:** render(<LoginForm />)

**Act:** fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'test@test.com' } })

**Assert:** expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled()

The Automated Test Runner

Watch a simulated React Testing Library test run. The test runner executes the code on the left line-by-line, which automatically interacts with the rendered component on the right, verifying its behavior without a human.

// LoginForm.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
test('enables submit button when valid', () => {
// Arrange
render(<LoginForm/>);
// Act
const input = screen.getByLabelText('Email');
fireEvent.change(input, { target: { value: 'user@test.com'} });
// Assert
expect(screen.getByRole('button')).toBeEnabled();
});
Virtual DOM Output

Login

TEST PASSED

A visualization of a React Testing Library execution. The test runner interacts with the DOM exactly as a user would (finding inputs by their label, typing characters, and asserting button states).

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1What is the core philosophy of React Testing Library (RTL)?
  2. 2Which type of test provides the highest confidence that the entire application works, but is the slowest to run?
  3. 3What is MSW (Mock Service Worker) primarily used for in React testing?

Remember this

  • Unit Tests (Vitest) are for pure functions.
  • Component Tests (RTL) are for isolated UI behavior.
  • E2E Tests (Playwright) are for full user flows in a real browser.
  • React Testing Library forces you to test the DOM (what the user sees), not the implementation (how it's coded).
  • Use MSW to cleanly mock API requests during component tests.

Done with this concept?

Mark it complete to track your progress. No login needed.