or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-testing.mdasync.mdcomponent-rendering.mdconfiguration.mdelement-queries.mdevent-simulation.mdevents.mdhook-testing.mdhooks.mdindex.mdqueries.mdquick-reference.mdrendering.md
tile.json

tessl/npm-testing-library--react

Simple and complete React DOM testing utilities that encourage good testing practices

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@testing-library/react@16.3.x

To install, run

npx @tessl/cli install tessl/npm-testing-library--react@16.3.1

index.mddocs/

React Testing Library

Testing utility for React emphasizing user-centric testing. Renders components in a test environment and provides queries for finding elements by accessible roles, labels, and text content.

Package Information

  • Package Name: @testing-library/react
  • Package Type: npm
  • Language: JavaScript/TypeScript
  • Installation: npm install --save-dev @testing-library/react @testing-library/dom

Peer Dependencies

  • react (^18.0.0 || ^19.0.0)
  • react-dom (^18.0.0 || ^19.0.0)
  • @testing-library/dom (^10.0.0)
  • @types/react (optional, ^18.0.0 || ^19.0.0)
  • @types/react-dom (optional, ^18.0.0 || ^19.0.0)

Core Imports

import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react';

Pure version (without auto-cleanup):

import { render, screen } from '@testing-library/react/pure';

CommonJS:

const { render, screen, fireEvent } = require('@testing-library/react');

Quick Reference

Essential Pattern

// Render component
render(<Component />);

// Find element (prefer role queries)
const button = screen.getByRole('button', { name: /submit/i });

// Interact
fireEvent.click(button);

// Assert
expect(screen.getByText(/success/i)).toBeInTheDocument();

// Async wait
await screen.findByText(/loaded/i);

Test Hook

const { result } = renderHook(() => useCustomHook());
act(() => result.current.action());
expect(result.current.value).toBe(expected);

Architecture

React Testing Library is built around several key concepts:

  • Rendering Utilities: Core render() and renderHook() functions for mounting React components and hooks in a test environment
  • Query System: Inherited from @testing-library/dom, provides accessible queries (getBy*, queryBy*, findBy*) that encourage testing from a user's perspective
  • Event System: Enhanced fireEvent with React-specific behaviors for simulating user interactions
  • act() Wrapper: Automatic wrapping of renders and updates in React's act() to ensure proper state updates
  • Auto-cleanup: Automatic unmounting and cleanup between tests (can be disabled via pure import)
  • Configuration: Global and per-test configuration for strict mode, queries, wrappers, and error handling

Core Capabilities

Rendering: rendering.md

function render(ui: React.ReactNode, options?: RenderOptions): RenderResult;

interface RenderOptions {
  container?: HTMLElement;              // Custom container (e.g., for <tbody>)
  baseElement?: HTMLElement;            // Base element for queries (defaults to container)
  wrapper?: React.ComponentType<{ children: React.ReactNode }>;  // Provider wrapper
  hydrate?: boolean;                    // SSR hydration mode
  legacyRoot?: boolean;                 // React 18 only, not supported in React 19+
  queries?: Queries;                    // Custom query set
  reactStrictMode?: boolean;            // Enable StrictMode
  onCaughtError?: (error: Error, errorInfo: { componentStack?: string }) => void;  // React 19+ only
  onRecoverableError?: (error: Error, errorInfo: { componentStack?: string }) => void;  // React 18+
}

interface RenderResult {
  container: HTMLElement;               // DOM container element
  baseElement: HTMLElement;             // Base element for queries
  rerender: (ui: React.ReactNode) => void;  // Re-render with new UI
  unmount: () => void;                  // Unmount and cleanup
  debug: (element?, maxLength?, options?) => void;  // Pretty-print DOM
  asFragment: () => DocumentFragment;   // Snapshot testing helper
  // All query functions bound to baseElement:
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  getAllByRole: (role: string, options?: ByRoleOptions) => HTMLElement[];
  queryByRole: (role: string, options?: ByRoleOptions) => HTMLElement | null;
  queryAllByRole: (role: string, options?: ByRoleOptions) => HTMLElement[];
  findByRole: (role: string, options?: ByRoleOptions) => Promise<HTMLElement>;
  findAllByRole: (role: string, options?: ByRoleOptions) => Promise<HTMLElement[]>;
  // Plus: getBy/queryBy/findBy LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, TestId
}

Key Patterns:

// Basic render
const { container, rerender } = render(<App />);

// With providers
render(<App />, {
  wrapper: ({ children }) => <Provider>{children}</Provider>
});

// Re-render with props
rerender(<App count={2} />);

Querying: queries.md

Query Priority: Role > Label > Placeholder > Text > DisplayValue > AltText > Title > TestId

// Synchronous queries
getByRole(role: string, options?: ByRoleOptions): HTMLElement;
getAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

queryByRole(role: string, options?: ByRoleOptions): HTMLElement | null;
queryAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

// Async queries (wait for element to appear)
findByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement>;
findAllByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement[]>;

// Available query types: Role, LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, TestId

Common Patterns:

// Prefer role queries (most accessible)
screen.getByRole('button', { name: /submit/i });
screen.getByRole('heading', { level: 1 });

// Form inputs
screen.getByLabelText('Email');
screen.getByPlaceholderText('Enter email');

// Text content
screen.getByText(/hello world/i);

// Negative assertions
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

// Scoped queries
const modal = screen.getByRole('dialog');
within(modal).getByRole('button', { name: /close/i });

Events: events.md

// Common events (automatically wrapped in act())
fireEvent.click(element, options?: MouseEventInit);
fireEvent.change(element, { target: { value: 'text' } });
fireEvent.submit(form);
fireEvent.focus(element);
fireEvent.blur(element);
fireEvent.keyDown(element, { key: 'Enter', code: 'Enter' });

Patterns:

// Click interaction
fireEvent.click(screen.getByRole('button'));

// Input change
fireEvent.change(screen.getByLabelText('Email'), {
  target: { value: 'user@example.com' }
});

// Keyboard
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

// Hover
fireEvent.mouseEnter(element);
fireEvent.mouseLeave(element);

Async Operations: async.md

// Wait for condition
function waitFor<T>(callback: () => T | Promise<T>, options?: {
  timeout?: number;  // default: 1000ms
  interval?: number; // default: 50ms
}): Promise<T>;

// Wait for removal
function waitForElementToBeRemoved<T>(
  element: T | (() => T),
  options?: { timeout?: number }
): Promise<void>;

// Async queries (shorthand for waitFor + getBy)
findByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement>;

Patterns:

// Wait for element to appear
const element = await screen.findByText(/loaded/i);

// Wait for condition
await waitFor(() => {
  expect(screen.getByText(/success/i)).toBeInTheDocument();
}, { timeout: 3000 });

// Wait for disappearance
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));

Hook Testing: hooks.md

function renderHook<Result, Props>(
  callback: (props: Props) => Result,
  options?: RenderHookOptions<Props>
): RenderHookResult<Result, Props>;

interface RenderHookResult<Result, Props> {
  result: { current: Result };
  rerender: (props?: Props) => void;
  unmount: () => void;
}

Patterns:

// Test hook
const { result } = renderHook(() => useCounter(0));

// Trigger updates (wrap in act)
act(() => result.current.increment());
expect(result.current.count).toBe(1);

// With context
const { result } = renderHook(() => useUser(), {
  wrapper: ({ children }) => <UserProvider>{children}</UserProvider>
});

// Re-render with props
const { result, rerender } = renderHook(
  ({ id }) => useFetch(id),
  { initialProps: { id: 1 } }
);
rerender({ id: 2 });

Configuration: configuration.md

function configure(config: Partial<Config>): void;

interface Config {
  reactStrictMode: boolean;        // default: false
  testIdAttribute: string;         // default: 'data-testid'
  asyncUtilTimeout: number;        // default: 1000ms
}

Setup Pattern:

// test-setup.js
import { configure } from '@testing-library/react';

configure({
  reactStrictMode: true,
  asyncUtilTimeout: 2000,
});

Common Type Definitions

interface ByRoleOptions {
  name?: string | RegExp;              // Accessible name filter
  description?: string | RegExp;       // Accessible description filter
  hidden?: boolean;                    // Include hidden elements (default: false)
  selected?: boolean;                  // Filter by selected state
  checked?: boolean;                   // Filter by checked state
  pressed?: boolean;                   // Filter by pressed state (toggle buttons)
  current?: boolean | string;          // Filter by current state (navigation)
  expanded?: boolean;                  // Filter by expanded state
  level?: number;                      // Filter by heading level (1-6)
  exact?: boolean;                     // Enable exact matching (default: true)
  normalizer?: (text: string) => string;  // Custom text normalizer
  queryFallbacks?: boolean;            // Enable query fallbacks
}

interface MatcherOptions {
  exact?: boolean;                     // Enable exact matching (default: true)
  normalizer?: (text: string) => string;  // Custom text normalizer
}

interface SelectorMatcherOptions extends MatcherOptions {
  selector?: string;                   // CSS selector to filter results
}

interface Queries {
  [key: string]: (...args: any[]) => any;
}

Production Patterns

Custom Render with Providers

import { render, RenderOptions } from '@testing-library/react';
import { ThemeProvider } from './theme';
import { QueryClientProvider } from '@tanstack/react-query';

export function renderWithProviders(
  ui: React.ReactElement,
  options?: Omit<RenderOptions, 'wrapper'>
) {
  return render(ui, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={queryClient}>
        <ThemeProvider>{children}</ThemeProvider>
      </QueryClientProvider>
    ),
    ...options,
  });
}

Testing Async Data Fetching

test('loads and displays data', async () => {
  render(<DataComponent />);

  expect(screen.getByText(/loading/i)).toBeInTheDocument();

  const data = await screen.findByText(/data loaded/i);
  expect(data).toBeInTheDocument();

  expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
});

Testing Form Submission

test('submits form with validation', async () => {
  const handleSubmit = jest.fn();
  render(<Form onSubmit={handleSubmit} />);

  const emailInput = screen.getByLabelText(/email/i);
  const submitButton = screen.getByRole('button', { name: /submit/i });

  fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
  fireEvent.click(submitButton);

  await waitFor(() => {
    expect(handleSubmit).toHaveBeenCalledWith({
      email: 'user@example.com'
    });
  });
});

Testing Modal Interactions

test('opens and closes modal', async () => {
  render(<App />);

  const openButton = screen.getByRole('button', { name: /open modal/i });
  fireEvent.click(openButton);

  const modal = await screen.findByRole('dialog');
  expect(modal).toBeInTheDocument();

  const closeButton = within(modal).getByRole('button', { name: /close/i });
  fireEvent.click(closeButton);

  await waitForElementToBeRemoved(modal);
});

Best Practices

  1. Query Priority: Use getByRole when possible for accessibility
  2. Async Queries: Use findBy* for async elements, waitFor for complex conditions
  3. Negative Assertions: Use queryBy* with .not.toBeInTheDocument()
  4. act() Wrapping: render/fireEvent/waitFor handle this automatically
  5. screen vs destructuring: Prefer screen for better error messages
  6. Scoped Queries: Use within() to limit query scope
  7. Real User Behavior: Test what users see and do, not implementation

Entry Points

Main Entry (@testing-library/react)

Default import with auto-cleanup and automatic act() environment setup. Use for standard test setups with Jest, Vitest, or similar test runners.

import { render, screen } from '@testing-library/react';
// Auto-cleanup runs after each test

Pure Entry (@testing-library/react/pure)

No auto-cleanup or automatic setup. Use when you need manual control over cleanup or test lifecycle.

import { render, screen, cleanup } from '@testing-library/react/pure';

afterEach(() => {
  cleanup(); // Manual cleanup
});

Dont-Cleanup-After-Each Entry

Alternative method to disable auto-cleanup by importing this file before the main import. Sets RTL_SKIP_AUTO_CLEANUP=true environment variable.

import '@testing-library/react/dont-cleanup-after-each';
import { render, screen, cleanup } from '@testing-library/react';

// Auto-cleanup is now disabled, manual cleanup required
afterEach(() => {
  cleanup();
});

Common Utility Functions

screen Object

Pre-bound queries to document.body for convenient access without destructuring render results.

const screen: {
  // All query functions
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other query functions

  // Debug utilities
  debug: (element?: HTMLElement, maxLength?: number) => void;
  logTestingPlaygroundURL: (element?: HTMLElement) => void;
};

within Function

Get queries bound to a specific element for scoped searches.

function within(element: HTMLElement): {
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other query functions
};

Debug Utilities

function prettyDOM(
  element?: HTMLElement,
  maxLength?: number,
  options?: any
): string;

function logRoles(container: HTMLElement): void;

Lifecycle Management

// Manual cleanup (auto-runs by default)
function cleanup(): void;

// Manual act() wrapping (usually automatic)
function act(callback: () => void | Promise<void>): Promise<void>;

Note: Most operations are automatically wrapped in act(), so manual use is rarely needed.