CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testing-library--react

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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.

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