What to Test
React Testing Library encourages testing components the way a user actually experiences them — rendering the component, interacting with it, and asserting on what appears on screen — rather than inspecting internal state or implementation details directly.
A Basic Component Test
import { render, screen } from "@testing-library/react";
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
test("renders a greeting with the given name", () => {
render(<Greeting name="Alice" />);
expect(screen.getByText("Hello, Alice!")).toBeInTheDocument();
});Simulating User Interaction
The userEvent library simulates real user interactions — clicks, typing, tabbing — more realistically than firing raw DOM events directly.
Testing a Click Interaction
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
test("increments the count when clicked", async () => {
const user = userEvent.setup();
render(<Counter />);
const button = screen.getByRole("button");
expect(button).toHaveTextContent("Count: 0");
await user.click(button);
expect(button).toHaveTextContent("Count: 1");
});Common Queries
| Query | Finds |
|---|---|
| getByRole | An element by its accessible role, like "button" or "heading" (preferred) |
| getByText | An element containing specific text |
| getByLabelText | A form input by its associated label |
| getByTestId | An element by a data-testid attribute (last resort) |
Testing Asynchronous Behavior
For components that fetch data or update after a delay, findBy* queries wait for an element to appear, and waitFor() waits for an arbitrary assertion to pass — both essential for testing anything involving useEffect or async state updates.
Testing an Async Component
test("shows the user's name after loading", async () => {
render(<UserProfile userId={1} />);
expect(screen.getByText("Loading...")).toBeInTheDocument();
const heading = await screen.findByRole("heading", { name: "Alice" });
expect(heading).toBeInTheDocument();
});Avoid Testing Implementation Details
A test that reaches into a component’s internal state or checks how many times a function was called tends to break whenever you refactor, even if the user-facing behavior didn’t change. Testing what’s rendered and how it responds to interaction is far more resilient.
Best Practice
Prefer getByRole over getByTestId whenever possible — it verifies the component is actually accessible (a real button, a real heading) while also being resilient to markup changes that don’t affect behavior.