Technology · React
React Testing Best Practices
Test React components effectively using React Testing Library, Vitest, and common testing patterns.
TL;DR
- 01Test user behavior, not implementation details.
- 02Use React Testing Library to query elements as users see them.
- 03Mock external dependencies but test component logic thoroughly.
Testing User Behavior
- Test what users see and do, not how components are built.
import { render, screen } from "@testing-library/react"; import Button from "./Button"; it("shows clicked message when button is clicked", async () => { render(<Button />); const button = screen.getByRole("button", { name: /click me/i }); await userEvent.click(button); expect(screen.getByText("You clicked!")).toBeInTheDocument(); }); - Query elements like users would: by text, role, label, not by className.
// Good: user sees this text screen.getByText("Welcome"); screen.getByRole("button", { name: /submit/i }); screen.getByLabelText("Email"); // Bad: implementation detail screen.getByTestId("submit-btn"); - Avoid testing component state or internal functions.
- Focus on inputs, outputs, and user interactions.
Setup and Utilities
- Configure Vitest with React Testing Library setup.
// vitest.config.js import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], test: { globals: true, environment: "jsdom" } }); - Create a custom render function to set up providers.
import { render } from "@testing-library/react"; export function renderWithProviders(ui) { return render(<ThemeProvider>{ui}</ThemeProvider>); } - Use this custom render in all tests to reduce boilerplate.
it("applies theme colors", () => { renderWithProviders(<Component />); // Component now has theme context });
Mocking Dependencies
- Mock external API calls to keep tests fast and isolated.
import { vi } from "vitest"; vi.mock("./api", () => ({ fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Alice" }) ) })); - Mock Next.js router for navigation testing.
vi.mock("next/router", () => ({ useRouter: vi.fn(() => ({ push: vi.fn(), pathname: "/" })) })); - Avoid mocking everything — only mock slow or external things.
- Test component logic thoroughly with real implementations.
Async Testing
- Use async/await for testing async code like API calls.
it("displays user data after fetching", async () => { render(<UserProfile userId="1" />); // Component fetches user data const name = await screen.findByText("Alice"); expect(name).toBeInTheDocument(); }); - Use findBy for elements that appear after async operations.
// findBy waits for the element to appear const element = await screen.findByText("Loaded"); // getBy fails immediately if element doesn't exist expect(() => screen.getByText("Loaded")).toThrow(); - Use waitFor for complex async scenarios.
await waitFor(() => { expect(screen.getByText("Success")).toBeInTheDocument(); });
Common Testing Patterns
- Test form submission with userEvent.
it("submits form with email", async () => { const user = userEvent.setup(); render(<LoginForm />); await user.type(screen.getByLabelText("Email"), "test@example.com"); await user.type(screen.getByLabelText("Password"), "password"); await user.click(screen.getByRole("button", { name: /login/i })); expect(screen.getByText("Welcome")).toBeInTheDocument(); }); - Test conditional rendering based on props.
it("shows success message when isSuccess is true", () => { render(<Alert isSuccess={true} message="All good!" />); expect(screen.getByText("All good!")).toBeInTheDocument(); }); - Test error handling and edge cases.
it("shows error when API fails", async () => { vi.mocked(fetchUser).mockRejectedValue(new Error("API failed")); render(<UserProfile userId="1" />); expect(await screen.findByText("Error loading user")).toBeInTheDocument(); });
Tip: Test user behavior and critical paths thoroughly, even if it means longer tests — catching real bugs is more important than test speed.
Warning: Avoid testing implementation details like component state or internal functions, since refactoring will break tests that depend on the old structure.