Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 140
Intermediate

React Testing Best Practices

Test React components effectively using React Testing Library, Vitest, and common testing patterns.

TL;DR

  1. 01Test user behavior, not implementation details.
  2. 02Use React Testing Library to query elements as users see them.
  3. 03Mock external dependencies but test component logic thoroughly.

Tips

  1. 01Test user behavior and critical paths thoroughly, even if it means longer tests — catching real bugs is more important than test speed.

Warnings

  1. 01Avoid testing implementation details like component state or internal functions, since refactoring will break tests that depend on the old structure.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 141
Intermediate

React Testing Best Practices

(continued)

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.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 142
Intermediate

React Testing Best Practices

(continued)

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
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 143
Intermediate

React Testing Best Practices

(continued)

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.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 144
Intermediate

React Testing Best Practices

(continued)

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();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 145
Intermediate

React Testing Best Practices

(continued)

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();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 146
Intermediate

React Testing Best Practices

(FAQ)

FAQ

React Testing Library is the current standard and is officially recommended by the React team. Enzyme is largely unmaintained and encourages querying internal state and component structure, which makes tests fragile when you refactor.

Use findBy* queries (e.g., await screen.findByText('Loaded')) or wrap assertions in await waitFor(() => expect(...)) after triggering the action. For realistic network mocking across many tests, consider Mock Service Worker (msw) instead of per-test fetch stubs.

userEvent simulates the full sequence of browser events a real user triggers (e.g., pointerover, focus, keydown, input, keyup on a type), while fireEvent dispatches a single synthetic event. Use userEvent from @testing-library/user-event for interactions like typing and clicking to catch bugs that depend on event ordering.

Call jest.mock('axios') at the top of your test file, then configure return values with axios.get.mockResolvedValue({ data: yourData }) per test. Reset mocks between tests using jest.clearAllMocks() in a beforeEach to prevent state from leaking across test cases.

Use getBy* when the element must exist synchronously (throws if missing), queryBy* when asserting an element is absent (returns null instead of throwing), and findBy* for elements that appear asynchronously (returns a promise). Prefer queries in this priority order: ByRole, ByLabelText, ByPlaceholderText, ByText — they mirror how users and assistive technology discover elements.