Component Testing

Testing Library

Testing Library helps you test components the way a user experiences them. Query by role and text, interact with real events, and stop testing implementation details.

intermediate13 min readUpdated Sep 15, 2026
Button.test.jsx
jsx
// Button.test.jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";

test("calls onClick when clicked", async () => {
  const onClick = vi.fn();
  render(<Button onClick={onClick}>Save</Button>);

  await userEvent.click(screen.getByRole("button", { name: "Save" }));

  expect(onClick).toHaveBeenCalledOnce();
});
Maintained by
The Testing Library team
Frameworks
React, Vue, Svelte, Angular
Queries
By role, label and text
Events
userEvent
Async
findBy, waitFor
Philosophy
Test the user experience

Why it matters

Why Testing Library changed component tests

Accessible by default

Querying by role and accessible name pushes you toward semantic, screen-reader-friendly markup.

Resistant to refactors

Tests that use the user's view of the page survive internal changes to components and state.

Works with any runner

Testing Library is runner-agnostic, pairing equally well with Vitest, Jest or another framework.

The big picture

The three ideas behind Testing Library

Query the DOM the way users find things, interact with real events, and assert on what the user sees.

Queries

Find

Locate elements by their role, label, text or other user-visible attributes.

userEvent

Interact

Simulate real user interactions such as typing, clicking and tabbing.

Assertions

Verify

Assert on what the user sees, with jest-dom matchers for DOM state.

Testing Library at a glance

The core of Testing Library

getByRole

The preferred query, matching how assistive technology finds elements.

getByLabelText

Find form controls by their associated label, exactly as users do.

getByText

Locate elements by their visible text content.

userEvent

Fire realistic events including focus, typing and keyboard interaction.

findBy and waitFor

Wait for elements to appear when updates are asynchronous.

jest-dom matchers

toBeInTheDocument, toHaveTextContent, toBeDisabled and more.

A short history

From Enzyme to user-centric tests

  1. 2018

    React Testing Library

    Kent C. Dodds releases a small library that tests components through the DOM.

    18
  2. 2019

    The Testing Library family

    The core is extracted and adapters appear for Vue, Svelte, Angular and others.

    19
  3. 2021

    userEvent matures

    userEvent becomes the recommended way to simulate interactions.

    21
  4. 2022

    Enzyme deprecated

    The implementation-detail approach declines and user-centric testing becomes the norm.

    22
  5. Today

    The standard

    Testing Library is the default approach to component testing across frameworks.

    Today

The complete guide

Testing Library: Everything you need to know

What is Testing Library?

Testing Library is a family of libraries that help you test UI components the way a user experiences them. Instead of reaching into a component’s internals, you render it, find elements the way a person would — by their role, label or visible text — and interact with them through realistic events.

The core idea is a reaction against implementation-detail testing. Older approaches rendered a component and then asserted on internal state, props or specific DOM structure. Those tests broke on every refactor while catching very few real bugs. Testing Library flips the perspective: if the user can see it and use it, you can test it, and the test survives changes to how the component works inside.

Rendering and querying

You render a component and then query the resulting DOM through screen.

// LoginForm.test.jsx
import { render, screen } from "@testing-library/react";
import { LoginForm } from "./LoginForm";

test("shows the email field", () => {
  render(<LoginForm />);

  expect(screen.getByLabelText("Email")).toBeInTheDocument();
  expect(screen.getByRole("button", { name: "Sign in" })).toBeInTheDocument();
});

screen is the recommended query surface. It searches the whole rendered document, which mirrors how a user sees the page rather than a particular container.

Choosing the right query

Testing Library provides many queries, and the order matters. Prefer the ones closest to the user’s perspective:

  1. getByRole — buttons, links, headings, inputs and more, matched by accessible name. This is the best default.
  2. getByLabelText — form controls found by their label.
  3. getByPlaceholderText — when a placeholder is the only available label.
  4. getByText — non-interactive text content.
  5. getByDisplayValue — the current value of a form element.
  6. getByAltText — images by their alt text.
  7. getByTitle and getByTestId — last resorts.
// queries.jsx
screen.getByRole("heading", { level: 1, name: "Dashboard" });
screen.getByLabelText("Password");
screen.getByText("Welcome back");
screen.getByAltText("Company logo");
screen.getByTestId("chart");

getByRole is powerful because it doubles as an accessibility check. If a control has no accessible name, the query fails — which means your markup is likely inaccessible too.

Each query has variants: getBy throws if nothing matches, queryBy returns null, and findBy returns a promise that waits. Use queryBy to assert that something is absent, and findBy after an async update.

Interacting with userEvent

userEvent simulates the full sequence of events a real user generates.

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

test("submits the query", async () => {
  const onSearch = vi.fn();
  render(<Search onSearch={onSearch} />);

  await userEvent.type(screen.getByLabelText("Search"), "vitest");
  await userEvent.click(screen.getByRole("button", { name: "Go" }));

  expect(onSearch).toHaveBeenCalledWith("vitest");
});

userEvent handles focus, keyboard and pointer events together, which catches bugs that a single fireEvent.change misses. Prefer it for everything except the rare case it does not cover.

Async behaviour

Most components update asynchronously, whether from a fetch, a timer or a transition. Use findBy and waitFor instead of arbitrary delays.

// Users.test.jsx
test("renders users after loading", async () => {
  render(<Users />);

  expect(screen.getByText("Loading…")).toBeInTheDocument();

  const items = await screen.findAllByRole("listitem");
  expect(items).toHaveLength(3);
});

findBy waits for a matching element and fails with a helpful message if it never appears. waitFor is for waiting on an assertion or a condition that is not a simple element lookup. Never use real timeouts; they make tests slow and flaky.

Assertions with jest-dom

The companion @testing-library/jest-dom package adds matchers that read naturally for DOM state.

// matchers.test.jsx
expect(button).toBeDisabled();
expect(input).toHaveValue("[email protected]");
expect(dialog).not.toBeInTheDocument();
expect(heading).toHaveTextContent("Dashboard");
expect(link).toHaveAttribute("href", "/docs");

These matchers make intent clearer than raw DOM property checks and produce better failure messages.

Accessibility-first testing

Because getByRole relies on accessible roles and names, writing tests this way surfaces accessibility problems early. If you cannot find a button by role, screen reader users cannot find it either. Adding getByRole and toHaveAccessibleName to your tests is one of the cheapest ways to improve accessibility, and it often replaces the need for a separate automated audit.

Testing Library across frameworks

The library has adapters for React, Vue, Svelte, Angular and others. The query API is shared, so the mental model transfers even if the setup differs. The React adapter is the most widely used and the one shown here.

Best practices

  • Query by role first, then label, then text; use test ids only as a last resort.
  • Use userEvent instead of fireEvent.
  • Await findBy and waitFor rather than using delays.
  • Assert on what the user sees, not on internal state.
  • Keep tests focused on one behaviour each.
  • Clean up automatically between tests; Testing Library does this by default.
  • Treat a failed getByRole as a hint about an accessibility problem.

Common mistakes

  • Reaching for container.querySelector and testing class names.
  • Using fireEvent when userEvent would catch more.
  • Arbitrary setTimeout waits instead of findBy.
  • Snapshotting the whole rendered tree.
  • Asserting on component state or props instead of the DOM.
  • Adding test ids to everything and losing the accessibility benefit.

Where to go next

Testing Library is the component layer of a solid test strategy. Run it with Vitest or Jest, deepen your React knowledge so components are testable, and cover complete journeys with Playwright. Then take a component you already built and write a test from the user’s perspective.

Finding an element

Query by role so the test matches how a user and assistive technology find the control, and fails when it is not accessible.

Prefer
const button = screen.getByRole("button", {
  name: "Save",
});
Avoid
const button = container.querySelector(
  ".btn.btn-primary",
);

Simulating interaction

userEvent fires the full sequence of events a real user produces, including focus and keyboard behaviour.

Prefer
await userEvent.type(
  screen.getByLabelText("Email"),
  "[email protected]",
);
Avoid
fireEvent.change(
  screen.getByLabelText("Email"),
  { target: { value: "[email protected]" } },
);

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Testing Library?

Our interactive tutorial walks you through Testing Library step by step — with quizzes and real code you can run in the browser.