Simple and complete React DOM testing utilities that encourage good testing practices
—
Renders React components in a test DOM environment with queries and lifecycle control.
function render(ui: React.ReactNode, options?: RenderOptions): RenderResult;
interface RenderOptions {
/**
* Custom container element. If not provided, a div appended to baseElement is used.
* For testing elements like <tbody>, provide a table element as container.
*/
container?: HTMLElement;
/**
* Base element for queries. Defaults to container if specified, otherwise document.body.
* Also used as the element printed by debug().
*/
baseElement?: HTMLElement;
/**
* Wrapper component to render around the UI.
* Useful for providers (Context, Router, Theme, etc.).
*/
wrapper?: React.JSXElementConstructor<{ children: React.ReactNode }>;
/**
* Use hydration instead of normal render (ReactDOM.hydrateRoot).
* Useful for server-side rendering scenarios.
*/
hydrate?: boolean;
/**
* Force synchronous ReactDOM.render instead of concurrent mode.
* Only supported in React 18. Not supported in React 19+.
* Throws an error if used with React 19 or later.
*/
legacyRoot?: boolean;
/**
* Custom query set to bind. Overrides default queries from @testing-library/dom.
*/
queries?: Queries;
/**
* Enable React.StrictMode wrapper around the component.
* Overrides global reactStrictMode config if specified.
*/
reactStrictMode?: boolean;
/**
* React 19+ only: Callback when React catches an error in an Error Boundary.
* Receives the error and errorInfo with componentStack.
* Only available in React 19 and later.
*/
onCaughtError?: (error: Error, errorInfo: { componentStack?: string }) => void;
/**
* Callback when React automatically recovers from errors.
* Receives error and errorInfo with componentStack.
* Some recoverable errors may include original error cause as error.cause.
* Available in React 18 and later.
*/
onRecoverableError?: (error: Error, errorInfo: { componentStack?: string }) => void;
}
interface RenderResult {
/**
* The DOM container element where the component was rendered
*/
container: HTMLElement;
/**
* The base element used for queries (container or document.body)
*/
baseElement: HTMLElement;
/**
* Re-renders the component with new UI
*/
rerender: (ui: React.ReactNode) => void;
/**
* Unmounts the component and cleans up
*/
unmount: () => void;
/**
* Pretty-prints the DOM for debugging
*/
debug: (
element?: HTMLElement | HTMLElement[],
maxLength?: number,
options?: any
) => void;
/**
* Returns a DocumentFragment of the container's innerHTML
*/
asFragment: () => DocumentFragment;
// All query functions from @testing-library/dom 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[]>;
getByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement;
getAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
queryByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement | null;
queryAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
findByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement>;
findAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement[]>;
getByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement;
getAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
queryByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
queryAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
findByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
findAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
getByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement;
getAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
queryByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement | null;
queryAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
findByText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement>;
findAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement[]>;
getByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement;
getAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement[];
queryByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
queryAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement[];
findByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
findAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
getByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement;
getAllByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
queryByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
queryAllByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
findByAltText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
findAllByAltText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
getByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement;
getAllByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement[];
queryByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
queryAllByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement[];
findByTitle: (title: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
findAllByTitle: (title: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
getByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement;
getAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement[];
queryByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
queryAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement[];
findByTestId: (testId: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
findAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
}const { container, getByRole, rerender } = render(<Counter initial={0} />);
expect(getByRole('button')).toBeInTheDocument();
// Re-render with new props
rerender(<Counter initial={5} />);const wrapper = ({ children }) => (
<ThemeProvider theme="dark">
<AuthProvider user={mockUser}>
{children}
</AuthProvider>
</ThemeProvider>
);
render(<App />, { wrapper });// For testing <tr>, <td>, etc.
const table = document.createElement('table');
const tbody = document.createElement('tbody');
table.appendChild(tbody);
render(<TableRow />, { container: tbody });const container = document.getElementById('root');
container.innerHTML = serverRenderedHTML;
render(<App />, { container, hydrate: true });render(<App />, { reactStrictMode: true });test('updates on prop change', () => {
const { rerender, getByText } = render(<Display value={1} />);
expect(getByText('1')).toBeInTheDocument();
rerender(<Display value={2} />);
expect(getByText('2')).toBeInTheDocument();
});test('cleans up on unmount', () => {
const cleanup = jest.fn();
function Component() {
useEffect(() => cleanup, []);
return <div>Content</div>;
}
const { unmount } = render(<Component />);
unmount();
expect(cleanup).toHaveBeenCalled();
});test('matches snapshot', () => {
const { asFragment } = render(<App />);
expect(asFragment()).toMatchSnapshot();
});const { debug, getByRole } = render(<App />);
debug(); // Print entire DOM
debug(getByRole('button')); // Print specific element
debug(getByRole('button'), 1000); // Limit output lengthAll queries available three ways:
// 1. From render result
const { getByRole } = render(<Component />);
const button = getByRole('button');
// 2. From screen (recommended)
import { screen } from '@testing-library/react';
render(<Component />);
const button = screen.getByRole('button');
// 3. From within (scoped)
const modal = screen.getByRole('dialog');
const button = within(modal).getByRole('button');render(<App />); // Uses ReactDOMClient.createRoot()render(<App />, { legacyRoot: true }); // Error in React 19+render(<App />, { hydrate: true }); // Uses hydrateRoot()// test-utils.tsx
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } }
});
export function renderWithProviders(
ui: React.ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) {
return render(ui, {
wrapper: ({ children }) => (
<BrowserRouter>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</BrowserRouter>
),
...options,
});
}
// Use in tests
import { renderWithProviders } from './test-utils';
test('renders with providers', () => {
renderWithProviders(<MyComponent />);
});type RendererableContainer = Element | DocumentFragment;
interface MatcherOptions {
exact?: boolean;
normalizer?: (text: string) => string;
}
interface SelectorMatcherOptions extends MatcherOptions {
selector?: string;
}
interface ByRoleOptions extends MatcherOptions {
name?: string | RegExp;
description?: string | RegExp;
hidden?: boolean;
selected?: boolean;
checked?: boolean;
pressed?: boolean;
current?: boolean | string;
expanded?: boolean;
level?: number;
queryFallbacks?: boolean;
}
interface Queries {
[key: string]: (...args: any[]) => any;
}The following type aliases are deprecated and provided for backward compatibility:
/** @deprecated Use RenderOptions instead */
type BaseRenderOptions<Q, Container, BaseElement> = RenderOptions;
/** @deprecated Use RenderOptions with hydrate: false instead */
interface ClientRenderOptions<Q, Container, BaseElement> extends RenderOptions {
hydrate?: false | undefined;
}
/** @deprecated Use RenderOptions with hydrate: true instead */
interface HydrateOptions<Q, Container, BaseElement> extends RenderOptions {
hydrate: true;
}Install with Tessl CLI
npx tessl i tessl/npm-testing-library--react