Home
React
Testing: React Testing Library
Daniel Nguyen
Daniel Nguyen
August 02, 2025
1 min

React Testing Library (RTL) encourages testing React components through their observable behavior rather than their internal implementation.

Built on DOM Testing Library, it helps you write readable, maintainable tests that resemble real user interactions.

1. Example: Testing a Multi-Step Form

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import MultiStepForm from "./MultiStepForm";
test("completes the multi-step form successfully", async () => {
const user = userEvent.setup();
const handleSubmit = jest.fn();
render(<MultiStepForm onSubmit={handleSubmit} />);
// Step 1
expect(
screen.getByRole("heading", { name: /step 1/i })
).toBeInTheDocument();
await user.type(
screen.getByRole("textbox", { name: /email/i }),
"test@mail.com"
);
await user.click(
screen.getByRole("button", { name: /next/i })
);
// Step 2
expect(
screen.getByRole("heading", { name: /step 2/i })
).toBeInTheDocument();
await user.type(
screen.getByLabelText(/password/i),
"123456"
);
await user.click(
screen.getByRole("button", { name: /submit/i })
);
expect(handleSubmit).toHaveBeenCalledWith({
email: "test@mail.com",
password: "123456",
});
});

This example assumes the form uses accessible headings and labels and calls onSubmit with the specified object.

2. Query Priority

Choose queries that reflect how users and assistive technologies identify elements.

QueryWhen to use
getByRoleButtons, headings, checkboxes, and other accessible elements
getByLabelTextForm fields associated with labels
getByPlaceholderTextInputs when a placeholder is the relevant identifier
getByTextVisible text that isn’t better queried by role
getByDisplayValueInputs with a specific current value

Use getByRole where practical because it also encourages accessible markup.

Query variants

  • getBy...: Expects one matching element immediately.
  • queryBy...: Returns null when no element matches; useful for asserting absence.
  • findBy...: Returns a Promise and waits for an element to appear asynchronously.

3. Common Assertions

These matchers are provided by @testing-library/jest-dom.

AssertionUse case
toBeInTheDocument()Element exists in the DOM
toHaveTextContent()Element contains expected text
toBeDisabled() / toBeEnabled()Control interaction state
toHaveValue()Form field value
toBeChecked()Checkbox or radio state
toHaveAttribute()HTML or accessibility attributes

4. What Should You Test in a Form?

  • Happy path: Valid data submits successfully.
  • Required validation: Missing required fields are rejected.
  • Invalid formats: Email or other input formats are validated.
  • Field-level errors: Error messages are displayed clearly.
  • Server errors: Failed requests are handled gracefully.
  • Loading states: Users receive feedback during submission.
  • Reset behavior: Fields return to the expected state.
  • Keyboard interaction: Users can navigate and submit using the keyboard.

Focus on behavior that matters to users rather than testing every implementation detail.

5. Create a Custom Render Helper

If your application requires shared providers, wrap RTL’s render function.

// test-utils.tsx
import {
render as rtlRender,
type RenderOptions,
} from "@testing-library/react";
import type { ReactElement, ReactNode } from "react";
import { ThemeProvider } from "../context/theme";
function Providers({ children }: { children: ReactNode }) {
return <ThemeProvider>{children}</ThemeProvider>;
}
function render(
ui: ReactElement,
options?: Omit<RenderOptions, "wrapper">
) {
return rtlRender(ui, {
...options,
wrapper: Providers,
});
}
export * from "@testing-library/react";
export { render };

Now tests can use your custom render without manually adding ThemeProvider every time.

import { render, screen } from "./test-utils";
test("renders the themed component", () => {
render(<MyComponent />);
expect(screen.getByRole("heading")).toBeInTheDocument();
});

Tags

#ReactTesting

Share

Daniel Nguyen

Daniel Nguyen

Frontend Developer

Frontend developer specializing in React, Next.js, and JavaScript. Writing practical guides on modern web development at Dev98.

Expertise

React
Next.js
JavaScript
TypeScript
Python

Social Media

githublinkedinyoutubewebsite

Related Posts

React Testing
Testing: JSDOM Mocks
August 04, 2025
1 min
Dev98

Dev98

React · Next.js · Web development