# Testing Components — React

Source: https://www.skillbyai.com/en/react/t-test

> Test what users see and do.

## Behaviour, not implementation

Good component tests render the component, interact as a user would (click, type), and assert on visible output and accessibility attributes, not on internal state. **React Testing Library** with **Vitest** or **Jest** is the common setup, using jsdom as a fake browser; `act()` ensures updates are applied before assertions. End-to-end tools such as Playwright cover full flows in real browsers. Test reducers and pure helpers directly as plain functions.

## Build with confidence

Test behaviour, organise code for growth, and review with a checklist.

![Three ideas: testing, project structure, checklist.](assets/figures/react/section-8-map.svg) — Figure 8.1 — Testing, structure and checklist.

## Testing a stepper's behaviour, run

I ran this with React 19.3 and react-dom 19.3 on Node.js 22, compiling JSX with esbuild 0.28. Browser-style examples use jsdom 29 as a fake DOM (imported from a small setup-dom.mjs file) and act() to apply updates. The test renders a stepper, clicks "increase" five times, and asserts that the value stops at the maximum of 3, the increase button is disabled and decrease is enabled; all assertions pass. Real projects would use React Testing Library queries such as getByRole for the same checks.

```jsx
import "./setup-dom.mjs";   // test setup: jsdom provides a browser-like DOM in Node
import { useState, act } from "react";
import { createRoot } from "react-dom/client";
import assert from "node:assert/strict";

function Stepper({ min = 0, max = 3 }) {
  const [n, setN] = useState(min);
  return <>
    <button aria-label="decrease" disabled={n <= min} onClick={() => setN(n - 1)}>-</button>
    <output>{n}</output>
    <button aria-label="increase" disabled={n >= max} onClick={() => setN(n + 1)}>+</button>
  </>;
}
const root = createRoot(document.getElementById("root"));
await act(() => root.render(<Stepper />));
const inc = document.querySelector('[aria-label="increase"]');
for (let i = 0; i < 5; i++) await act(() => inc.click());
assert.equal(document.querySelector("output").textContent, "3");      // clamps at max
assert.equal(inc.disabled, true);
assert.equal(document.querySelector('[aria-label="decrease"]').disabled, false);
console.log("all assertions passed");
```

Output:

```
all assertions passed
```

## Query by role and label

Selecting elements by role and accessible name makes tests resilient and nudges you towards accessible markup.

**Quiz:** What should component tests focus on?

- [x] What users see and can do
- [ ] Internal state variable names
- [ ] The number of hooks
- [ ] CSS class order

*Answer:* What users see and can do. Behavioural tests survive refactors.
