Lesson 23 / 25
Testing Components
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.
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.
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.
Quick check: What should component tests focus on?
- 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.