Learn / Frameworks / React / Testing Components

React · Lesson 12 of 15

Testing Components

Test behaviour with Vitest and Testing Library, not implementation details.

  • Intermediate
  • 17 min read
  • 3 objectives

Before this lessonLesson 11: Suspense and Error Boundaries

What you will learn

  • Render and query the DOM
  • Fire user events
  • Mock a fetch

Your Progress

0 of 15 lessons 0%

  • Lessons0 / 15
  • Completed0
  • Est. time left~ 4 hours

Create a free account to keep your progress on every device.

Test what the user sees and does, not the internals of a hook. Vitest (or Jest) runs the tests; Testing Library renders components into a fake DOM and queries them the way a person would.

Setup

npm install -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom
// vitest.config.js
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
  plugins: [react()],
  test: { environment: "jsdom", setupFiles: "./src/setupTests.js" },
});
// src/setupTests.js
import "@testing-library/jest-dom/vitest";

Render, query, click

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

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

Prefer getByRole, getByLabelText and getByText over getByTestId. If a test cannot find a button by its name, neither can a screen reader.

Mocking the network

test("shows users", async () => {
  global.fetch = vi.fn().mockResolvedValue({
    ok: true,
    json: async () => [{ id: 1, name: "Ada" }],
  });
  render(<Users />);
  expect(await screen.findByText("Ada")).toBeInTheDocument();
});
Up next · Lesson 13App-wide State with ZustandShare global state without wrapping the tree in providers.