React Advanced: Performance, Patterns and Production
Testing React Apps
In this lesson you will
- Explain what to test and what not to test
- Write a component test with Testing Library
- Test asynchronous behaviour and mock network calls
Introduction
Tests let you change code with confidence. For React, the most useful approach is to test behaviour from the user's point of view: what they see and do, not how the component is built inside.
The guiding principle
The more your tests resemble how users use the software, the more confidence they give.
So avoid testing internal state or implementation details. Test: "when I click Add, the item appears". Your tests then survive refactoring.
The tools
- A test runner such as Vitest or Jest runs the tests.
- React Testing Library renders components and offers queries to find elements the way users and assistive technology do.
- user-event simulates real interactions, such as typing and clicking.
- jest-dom adds helpful checks such as
toBeInTheDocument().
A first test
For this component:
React (JSX)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}A test, shown for reading:
React (JSX)
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test("increases the count when the button is clicked", async () => {
const user = userEvent.setup();
render(<Counter />);
expect(screen.getByText("Count: 0")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Increase" }));
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});Notice that the test finds the button by its role and visible name, just like a user or screen reader would.
Quick quiz
Which query best matches how a user finds a button?
Which query to use
Prefer, in this order:
getByRole(buttons, headings, links, form controls)getByLabelTextfor form fieldsgetByTextgetByTestIdas a last resort, using adata-testidattribute
Variants: getBy throws if not found, queryBy returns null (to assert something is absent), and findBy waits for something to appear.
Async behaviour and mocking
When a component loads data, wait for the result:
React (JSX)
test("shows users after loading", async () => {
render(<UserList />);
expect(screen.getByText("Loading...")).toBeInTheDocument();
expect(await screen.findByText("Ada")).toBeInTheDocument();
});Do not call the real network in tests. Mock it, either by replacing fetch with a fake or by using a tool such as Mock Service Worker, which intercepts requests and returns controlled responses. Test the error and empty cases too.
What to test
- Key user flows: sign in, add to cart, submit a form
- Edge cases: empty lists, errors, validation messages
- Accessibility basics: elements reachable by role and label
- Complex logic: put pure logic in plain functions and unit-test them directly
Avoid:
- Testing React itself or third-party libraries
- Snapshot tests of large trees, which break often and teach little
- Tests that depend on the order of other tests
Beyond component tests
- Integration tests combine several components and pages.
- End-to-end tests with tools such as Playwright or Cypress drive a real browser through the full app. Fewer of them, covering the most important journeys.
- Run tests automatically in continuous integration on every pull request.
Try it yourself
Write a test for the signup form from the Forms lesson: it should show an error when the email is invalid, and no error when valid input is submitted.
Summary
- Test behaviour from the user's point of view, not implementation details.
- Use Testing Library queries by role and label, user-event for interactions and
findByfor async results. - Mock the network, test errors and empty states, and run tests in CI.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading