Publié · en amélioration
Guide React · 5/6
Ce chapitre n'est disponible qu'en anglais pour le moment.
Tests for a React app usually come in two layers. Components and hooks are checked quickly with Vitest and React Testing Library, while complete user flows run in a real browser with Playwright.
Vitest reuses your Vite configuration, so it needs almost no extra transform setup. Install it together with jsdom, which emulates the browser DOM, and the Testing Library packages.
npm install -D vitest jsdom @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-domAdd a test section to vite.config.ts. The reference comment at the top lets TypeScript know about the test field.
/// <reference types="vitest/config" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
setupFiles: "./src/test/setup.ts",
include: ["src/**/*.test.{ts,tsx}"],
},
});With globals: true, Testing Library automatically cleans up rendered output after each test. Setting include keeps Vitest from picking up the Playwright tests you will add later. The setup file src/test/setup.ts registers DOM-specific matchers such as toBeInTheDocument().
import "@testing-library/jest-dom/vitest";Finally, add "test": "vitest" to the scripts in package.json. npm test runs in watch mode and re-runs related tests on every save; in CI, run once with npx vitest run.
Testing Library steers you toward testing what the user sees rather than implementation details. Find elements by accessible information such as getByRole or getByLabelText whenever possible, and simulate clicks and typing with user-event, which mimics real user interaction closely.
// src/Counter.tsx
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>;
}// src/Counter.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { Counter } from "./Counter";
describe("Counter", () => {
it("increments when clicked", async () => {
const user = userEvent.setup();
render(<Counter />);
const button = screen.getByRole("button", { name: "Clicked 0 times" });
await user.click(button);
expect(button).toHaveTextContent("Clicked 1 times");
});
});Components that load data should receive mocked responses instead of hitting the network. Replace the global fetch with vi.spyOn, and use findBy... queries, which wait for elements that appear later. The UserName component from the previous chapter serves as the example.
import { render, screen } from "@testing-library/react";
import { afterEach, expect, it, vi } from "vitest";
import { UserName } from "./UserName";
afterEach(() => {
vi.restoreAllMocks();
});
it("loads and shows the user's name", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ id: 1, name: "Ada" })),
);
render(<UserName userId={1} />);
expect(screen.getByText("Loading…")).toBeInTheDocument();
expect(await screen.findByText("Ada")).toBeInTheDocument();
});For apps with many API calls, Mock Service Worker (MSW) intercepts requests at the network level, so tests and the dev server can share the same mock API.
To test a hook without any UI, use renderHook. Wrap calls that update state in act so React applies every update before you assert on the result.
import { act, renderHook } from "@testing-library/react";
import { expect, it } from "vitest";
import { useTodos } from "./useTodos";
it("adds a todo", () => {
const { result } = renderHook(() => useTodos());
act(() => result.current.add("Buy milk"));
expect(result.current.todos).toHaveLength(1);
expect(result.current.todos[0].text).toBe("Buy milk");
});Playwright launches real Chromium, Firefox and WebKit browsers and drives your app the way a user would. The init command creates the config file and an example test, and installs the browsers in one go.
npm init playwright@latestSetting webServer in playwright.config.ts starts the dev server automatically before the tests run.
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
use: { baseURL: "http://localhost:5173" },
webServer: {
command: "npm run dev",
url: "http://localhost:5173",
reuseExistingServer: !process.env.CI,
},
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
});// e2e/counter.spec.ts (assumes the home page renders Counter)
import { expect, test } from "@playwright/test";
test("the counter button works", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: /Clicked/ }).click();
await expect(page.getByRole("button", { name: "Clicked 1 times" })).toBeVisible();
});Run the suite with npx playwright test; add --ui to step through each action with a live view while debugging. Playwright's expect retries until the condition holds, so there is no need for arbitrary sleeps.
user-event.findBy... queries, and mock the network with vi.spyOn or MSW.webServer option starts the dev server automatically for end-to-end runs.
0 commentaire
Se connecter · Connectez-vous pour laisser un commentaire.
Soyez le premier à commenter.