Testing — Jest/Vitest
Every project so far has been verified by hand — running the app and clicking around to confirm something works. That's fine for small projects, but doesn't scale, and gives nothing to catch a regression introduced weeks later by an unrelated change. React Testing Library (paired with a test runner — Jest, or Vite's own Vitest) lets that same kind of verification be written once and run automatically.
describe, test, expect all work the same way). Since every project in this course has used Vite, Vitest is the natural fit going forward — everything in this chapter applies equally to either, since React Testing Library itself doesn't care which one is running it.
The Core Philosophy
React Testing Library is built around one guiding rule: test what a user actually sees and does, not a component's internal implementation. That means querying for visible text, labels, and accessible roles — never reaching into a component's state, props, or internal function calls directly. A test written this way keeps passing through a refactor that changes how a component works internally, as long as it still behaves the same way from the outside — exactly the kind of test that's actually worth having.
A First Test
render(<Greeting name="Philip" />) renders the component into a virtual DOM the test can inspect; screen.getByText(...) searches that rendered output for matching text, throwing immediately if nothing matches; expect(...).toBeInTheDocument() is the actual assertion — confirming the element was actually found.
Simulating User Interaction
userEvent simulates real user interaction more faithfully than directly firing a raw DOM event — clicking, typing, and focusing all go through the same library, recommended over the lower-level fireEvent for exactly that reason. getByRole("button", { name: "+1" }) finds the button by its accessible role and visible label — the same way a screen reader or a person scanning the page would identify it, rather than relying on a CSS class or test-specific attribute.
Testing a Form
vi.fn() (or jest.fn()) creates a mock function — a fake function the test can pass in as a prop and later inspect, asking "was this called, and with what?" without needing a real implementation behind it. Passing a mock as onSubmit lets the test confirm SearchForm calls it correctly with the typed value, without needing the component to actually be connected to a real API anywhere.
useState to useReducer) even though the component's actual behavior never changed. Querying by role, label, and visible text — the same things a real user would see and interact with — keeps a test meaningful regardless of what changes underneath.
| Query | Prefer it when... |
|---|---|
| getByRole | Almost always the first choice — matches how assistive tech sees the page |
| getByLabelText | Form fields specifically |
| getByText | Plain visible text with no clearer role/label |
| getByTestId | Last resort — nothing else can uniquely identify the element |
Coding Challenges
Write a test for a Greeting component (accepting a name prop) confirming it renders the expected text for at least two different names.
📄 View solutionWrite a test for a Counter component (from Fundamentals Chapter 3) using userEvent to click the +1 button twice, asserting the displayed count updates correctly each time.
📄 View solutionWrite a test for a controlled search input + submit button, using userEvent.type to type a value and a mock function (vi.fn()/jest.fn()) passed as an onSubmit prop, asserting it was called with the typed value.
📄 View solutionChapter 4 Quick Reference
- render() mounts a component for testing; screen.getByX queries its rendered output
- userEvent simulates real clicks/typing — preferred over the lower-level
fireEvent - vi.fn() / jest.fn() — a mock function, for asserting it was called (and with what)
- Query priority: getByRole > getByLabelText > getByText > getByTestId (last resort)
- Test what a user sees/does — never a component's internal state or implementation details
- Vitest (Vite's test runner) and Jest share nearly identical syntax — everything here applies to both
- Next chapter: advanced patterns — compound components, render props, higher-order components