PluginBench
Skill
Pass
Audit score 90

react-testing-library

itechmeat/llm-code

User-centric React component testing with accessibility-first queries and user-event simulation.

What is react-testing-library?

React Testing Library provides utilities for testing React components by simulating how users interact with them. Use it when writing component tests, selecting elements by role/label/text, simulating user events, or testing async UI behavior.

  • Query elements by ARIA role, label, text, alt text, or test ID with getBy/queryBy/findBy variants
  • Simulate user interactions (click, type, keyboard, hover, select) via @testing-library/user-event
  • Handle async UI updates with waitFor, findBy queries, and waitForElementToBeRemoved
  • Test React hooks with renderHook and act utilities
  • Render components with custom providers using wrapper option
  • Debug tests with screen.debug(), logRoles(), and prettyDOM output

How to install react-testing-library

npx skills add https://github.com/itechmeat/llm-code --skill react-testing-library
Prerequisites
  • npm install --save-dev @testing-library/react @testing-library/dom
  • Recommended: @testing-library/user-event and @testing-library/jest-dom
  • React 19 requires v16.1.0 or later
Claude Code
Cursor
Windsurf
Cline

How to use react-testing-library

  1. 1.Import render and screen from @testing-library/react in your test file
  2. 2.Call render() with your component to mount it in the test DOM
  3. 3.Use screen.getByRole(), screen.getByLabelText(), or screen.getByText() to find elements
  4. 4.Create a userEvent.setup() instance and use await user.click(), user.type(), etc. to simulate interactions
  5. 5.Use await screen.findByText() or await waitFor() to handle async updates
  6. 6.Assert outcomes with jest-dom matchers like toBeInTheDocument(), toBeVisible(), toBeDisabled()

Use cases

Good for
  • Write unit tests for form components with user input and validation
  • Test modal dialogs and async data loading with proper wait patterns
  • Verify accessibility by querying elements the way assistive technology does
  • Test custom hooks in isolation with renderHook
  • Debug component rendering issues with built-in logging tools
Who it's for
  • React developers writing component tests
  • QA engineers testing React applications
  • Teams prioritizing accessibility in testing
  • Developers using Jest or Vitest as test runners

react-testing-library FAQ

When should I use getBy vs queryBy vs findBy?

Use getBy when the element must exist (throws if not found); queryBy when it may not exist (returns null); findBy when it appears asynchronously (waits and returns a promise).

Why should I use userEvent instead of fireEvent?

userEvent simulates realistic user interactions (typing character-by-character, proper event sequences) whereas fireEvent dispatches events directly, missing browser behavior.

How do I test elements inside a modal or container?

Use the within() utility: const modal = screen.getByRole('dialog'); const btn = within(modal).getByRole('button', { name: /submit/i });

What's the preferred query order?

Prefer queries accessible to everyone: getByRole (best), getByLabelText, getByText; then semantic queries like getByAltText; use getByTestId only as a last resort.

How do I test custom React hooks?

Use renderHook(() => useMyHook()) from @testing-library/react, access result.current, and wrap state changes in act() to trigger updates.

Full instructions (SKILL.md)

Source of truth, from itechmeat/llm-code.


name: react-testing-library description: "React Testing Library: user-centric component testing with queries, user-event simulation, async utilities, and accessibility-first API. Use when writing React component tests, selecting elements by role/label/text, simulating user events, or testing async UI behavior. Keywords: React Testing Library, @testing-library/react, user-event, queries, render." metadata: version: "16.3.3" release_date: "2026-09-04"

React Testing Library Skill

Quick Navigation

TopicLink
Queriesreferences/queries.md
User Eventsreferences/user-events.md
APIreferences/api.md
Asyncreferences/async.md
Debuggingreferences/debugging.md
Configreferences/config.md

Installation

Install: npm install --save-dev @testing-library/react @testing-library/dom. Recommended extras: @testing-library/user-event and @testing-library/jest-dom. React 19 requires v16.1.0+.

Core Philosophy

"The more your tests resemble the way your software is used, the more confidence they can give you."

Avoid testing:

  • Internal state of components
  • Internal methods
  • Lifecycle methods
  • Child component implementation details

Test instead:

  • What users see and interact with
  • Behavior from user's perspective
  • Accessibility (queries by role, label)

Query Priority

Use queries in this order of preference:

1. Accessible to Everyone (Preferred)

// Best — by ARIA role
getByRole("button", { name: /submit/i });
getByRole("textbox", { name: /email/i });

// Form fields — by label
getByLabelText("Email");

// Non-interactive content — by text
getByText("Welcome back!");

2. Semantic Queries

// Images
getByAltText("Company logo");

// Title attribute (less reliable)
getByTitle("Close");

3. Test IDs (Escape Hatch)

// Only when other queries don't work
getByTestId("custom-element");

Query Types

TypeNo Match1 Match>1 MatchAsync
getBy...throwreturnthrowNo
queryBy...nullreturnthrowNo
findBy...throwreturnthrowYes
getAllBy...throwarrayarrayNo
queryAllBy...[]arrayarrayNo
findAllBy...throwarrayarrayYes

When to use:

  • getBy* — element exists
  • queryBy* — element may not exist (assertions like expect(...).not.toBeInTheDocument())
  • findBy* — element appears asynchronously

Basic Test Pattern

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

test("shows greeting after login", async () => {
  const user = userEvent.setup();
  render(<App />);

  // Act — simulate user interactions
  await user.type(screen.getByLabelText(/username/i), "john");
  await user.click(screen.getByRole("button", { name: /login/i }));

  // Assert — verify outcome
  expect(await screen.findByText(/welcome, john/i)).toBeInTheDocument();
});

User Events

Always use @testing-library/user-event over fireEvent:

import userEvent from "@testing-library/user-event";

test("user interactions", async () => {
  const user = userEvent.setup();

  // Click
  await user.click(element);
  await user.dblClick(element);
  await user.tripleClick(element);

  // Type
  await user.type(input, "Hello");
  await user.clear(input);

  // Select
  await user.selectOptions(select, ["option1", "option2"]);

  // Keyboard
  await user.keyboard("{Enter}");
  await user.keyboard("[ShiftLeft>]a[/ShiftLeft]"); // Shift+A

  // Clipboard
  await user.copy();
  await user.paste();

  // Pointer
  await user.hover(element);
  await user.unhover(element);
});

Async Patterns

waitFor — Retry Until Success

await waitFor(() => {
  expect(screen.getByText("Loaded")).toBeInTheDocument();
});

// With options
await waitFor(() => expect(callback).toHaveBeenCalled(), {
  timeout: 5000,
  interval: 100,
});

findBy — Built-in waitFor

// Equivalent to: await waitFor(() => getByText('Loaded'))
const element = await screen.findByText("Loaded");

waitForElementToBeRemoved

await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));

Common Patterns

Custom Render with Providers

// test-utils.tsx
import { render } from "@testing-library/react";
import { ThemeProvider } from "./ThemeProvider";
import { AuthProvider } from "./AuthProvider";

function AllProviders({ children }) {
  return (
    <ThemeProvider>
      <AuthProvider>{children}</AuthProvider>
    </ThemeProvider>
  );
}

const customRender = (ui, options) => render(ui, { wrapper: AllProviders, ...options });

export * from "@testing-library/react";
export { customRender as render };

Testing Hooks

import { renderHook, act } from "@testing-library/react";

test("useCounter increments", () => {
  const { result } = renderHook(() => useCounter());

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

Rerender with New Props

const { rerender } = render(<Counter count={1} />);
expect(screen.getByText("Count: 1")).toBeInTheDocument();

rerender(<Counter count={2} />);
expect(screen.getByText("Count: 2")).toBeInTheDocument();

Query Within Container

import { within } from "@testing-library/react";

const modal = screen.getByRole("dialog");
const submitBtn = within(modal).getByRole("button", { name: /submit/i });

Debugging

// Print entire DOM
screen.debug();

// Print specific element
screen.debug(screen.getByRole("button"));

// Log available roles
import { logRoles } from "@testing-library/react";
logRoles(container);

// With prettyDOM options
screen.debug(undefined, 10000); // max length

jest-dom Matchers

import "@testing-library/jest-dom";

expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeEnabled();
expect(element).toBeDisabled();
expect(element).toHaveTextContent("Hello");
expect(element).toHaveValue("input value");
expect(element).toHaveAttribute("href", "/home");
expect(element).toHaveClass("active");
expect(element).toHaveFocus();
expect(element).toBeChecked();

Configuration

import { configure } from "@testing-library/react";

configure({
  // Custom test ID attribute
  testIdAttribute: "data-my-test-id",

  // Async timeout
  asyncUtilTimeout: 5000,

  // Default hidden
  defaultHidden: true,

  // Throw suggestions (debugging)
  throwSuggestions: true,
});

❌ Prohibitions (Anti-patterns)

// ❌ Don't query by class/id
container.querySelector(".my-class");

// ❌ Don't use container.firstChild
const { container } = render(<Component />);
expect(container.firstChild).toHaveClass("active");

// ❌ Don't use fireEvent when userEvent works
fireEvent.click(button); // Use userEvent.click instead

// ❌ Don't test implementation details
expect(component.state.loading).toBe(false);

// ❌ Don't use waitFor with findBy
await waitFor(() => screen.findByText("x")); // findBy already waits

// ❌ Don't assert inside waitFor callback (unless necessary)
await waitFor(() => {
  expect(mockFn).toHaveBeenCalled(); // OK - need to wait for call
});

✅ Best Practices

// ✅ Use screen for all queries
import { render, screen } from "@testing-library/react";
render(<Component />);
screen.getByRole("button"); // Good

// ✅ Prefer userEvent over fireEvent
const user = userEvent.setup();
await user.click(button);

// ✅ Use findBy for async elements
const element = await screen.findByText("Loaded");

// ✅ Use queryBy for non-existence assertions
expect(screen.queryByText("Error")).not.toBeInTheDocument();

// ✅ Use within for scoped queries
const form = screen.getByRole("form");
within(form).getByLabelText("Email");

// ✅ Use accessible queries (role, label, text)
getByRole("button", { name: /submit/i });

TextMatch Options

// Exact match (default)
getByText("Hello World");

// Substring match
getByText("llo Worl", { exact: false });

// Regex
getByText(/hello world/i);

// Custom function
getByText((content, element) => {
  return element.tagName === "SPAN" && content.startsWith("Hello");
});

Quick Reference

ImportUsage
renderRender component to DOM
screenQuery the rendered DOM
cleanupUnmount components (auto in Jest)
actWrap state updates
renderHookTest custom hooks
withinScope queries to element
waitForRetry until assertion passes
configureSet global options
userEvent.setup()Create user event instance

Links