~/hackweb.dev
Testing React
Quiz
...

Testing React

intermediate · updated Tue Sep 08 2026Contribute

Write tests for React components with React Testing Library.

Testing React

Test what users see and do, not how components work internally. React Testing Library (RTL) encourages this approach.

What to Test

  • Renders correctly with different data
  • Responds to user interactions (clicks, input)
  • Shows the right content based on props or state
  • Handles loading, error, and empty states

Don’t test implementation details like internal state or hook calls.

Setup

Install the required packages.

npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

Configure Vitest with a DOM environment.

// vitest.config.js
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "jsdom",
    setupFiles: ["./setup.js"],
  },
});
// setup.js
import "@testing-library/jest-dom";

Basic Test

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "./Counter";

test("increments count on click", async () => {
  render(<Counter />);
  const button = screen.getByRole("button", { name: /count/i });
  await userEvent.click(button);
  expect(button).toHaveTextContent("1");
});

Querying Elements

Use queries that reflect what users see.

screen.getByRole("button", { name: "Submit" });  // Accessible role
screen.getByText("Welcome");                      // Text content
screen.getByLabelText("Email");                   // Form labels
screen.getByPlaceholderText("Search");            // Placeholder
screen.queryByText("Not here");                   // Returns null if missing

Prefer getByRole and getByLabelText over getByTestId.

Testing Async Behavior

test("loads user data", async () => {
  render(<UserProfile userId={1} />);
  expect(await screen.findByText("John Doe")).toBeInTheDocument();
});

Use findBy for elements that appear after an async operation.

Mocking

Mock API calls with vi.fn().

vi.mock("./api", () => ({
  fetchUser: vi.fn(),
}));

import { fetchUser } from "./api";

test("displays error on failure", async () => {
  fetchUser.mockRejectedValue(new Error("Network error"));
  render(<UserProfile userId={1} />);
  expect(await screen.findByText("Error")).toBeInTheDocument();
});

Best Practices

  • Test user behavior, not implementation
  • Use accessible queries (getByRole, getByLabelText)
  • Use userEvent over fireEvent for realistic interactions
  • Mock at the network level, not component internals
  • Keep tests focused — one behavior per test

Common Mistakes

  • Testing internal state or hook return values
  • Using getByTestId as the first choice
  • Writing tests that depend on rendering order
  • Not cleaning up between tests (RTL handles this automatically)
  • Over-mocking — mock only what you need