0
# Component Rendering
1
2
Renders React components in a test DOM environment with queries and lifecycle control.
3
4
## API
5
6
```typescript { .api }
7
function render(ui: React.ReactNode, options?: RenderOptions): RenderResult;
8
9
interface RenderOptions {
10
/**
11
* Custom container element. If not provided, a div appended to baseElement is used.
12
* For testing elements like <tbody>, provide a table element as container.
13
*/
14
container?: HTMLElement;
15
16
/**
17
* Base element for queries. Defaults to container if specified, otherwise document.body.
18
* Also used as the element printed by debug().
19
*/
20
baseElement?: HTMLElement;
21
22
/**
23
* Wrapper component to render around the UI.
24
* Useful for providers (Context, Router, Theme, etc.).
25
*/
26
wrapper?: React.JSXElementConstructor<{ children: React.ReactNode }>;
27
28
/**
29
* Use hydration instead of normal render (ReactDOM.hydrateRoot).
30
* Useful for server-side rendering scenarios.
31
*/
32
hydrate?: boolean;
33
34
/**
35
* Force synchronous ReactDOM.render instead of concurrent mode.
36
* Only supported in React 18. Not supported in React 19+.
37
* Throws an error if used with React 19 or later.
38
*/
39
legacyRoot?: boolean;
40
41
/**
42
* Custom query set to bind. Overrides default queries from @testing-library/dom.
43
*/
44
queries?: Queries;
45
46
/**
47
* Enable React.StrictMode wrapper around the component.
48
* Overrides global reactStrictMode config if specified.
49
*/
50
reactStrictMode?: boolean;
51
52
/**
53
* React 19+ only: Callback when React catches an error in an Error Boundary.
54
* Receives the error and errorInfo with componentStack.
55
* Only available in React 19 and later.
56
*/
57
onCaughtError?: (error: Error, errorInfo: { componentStack?: string }) => void;
58
59
/**
60
* Callback when React automatically recovers from errors.
61
* Receives error and errorInfo with componentStack.
62
* Some recoverable errors may include original error cause as error.cause.
63
* Available in React 18 and later.
64
*/
65
onRecoverableError?: (error: Error, errorInfo: { componentStack?: string }) => void;
66
}
67
68
interface RenderResult {
69
/**
70
* The DOM container element where the component was rendered
71
*/
72
container: HTMLElement;
73
74
/**
75
* The base element used for queries (container or document.body)
76
*/
77
baseElement: HTMLElement;
78
79
/**
80
* Re-renders the component with new UI
81
*/
82
rerender: (ui: React.ReactNode) => void;
83
84
/**
85
* Unmounts the component and cleans up
86
*/
87
unmount: () => void;
88
89
/**
90
* Pretty-prints the DOM for debugging
91
*/
92
debug: (
93
element?: HTMLElement | HTMLElement[],
94
maxLength?: number,
95
options?: any
96
) => void;
97
98
/**
99
* Returns a DocumentFragment of the container's innerHTML
100
*/
101
asFragment: () => DocumentFragment;
102
103
// All query functions from @testing-library/dom bound to baseElement
104
getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
105
getAllByRole: (role: string, options?: ByRoleOptions) => HTMLElement[];
106
queryByRole: (role: string, options?: ByRoleOptions) => HTMLElement | null;
107
queryAllByRole: (role: string, options?: ByRoleOptions) => HTMLElement[];
108
findByRole: (role: string, options?: ByRoleOptions) => Promise<HTMLElement>;
109
findAllByRole: (role: string, options?: ByRoleOptions) => Promise<HTMLElement[]>;
110
111
getByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement;
112
getAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
113
queryByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement | null;
114
queryAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
115
findByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement>;
116
findAllByLabelText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement[]>;
117
118
getByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement;
119
getAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
120
queryByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
121
queryAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
122
findByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
123
findAllByPlaceholderText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
124
125
getByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement;
126
getAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
127
queryByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement | null;
128
queryAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => HTMLElement[];
129
findByText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement>;
130
findAllByText: (text: string | RegExp, options?: SelectorMatcherOptions) => Promise<HTMLElement[]>;
131
132
getByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement;
133
getAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement[];
134
queryByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
135
queryAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => HTMLElement[];
136
findByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
137
findAllByDisplayValue: (value: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
138
139
getByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement;
140
getAllByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
141
queryByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
142
queryAllByAltText: (text: string | RegExp, options?: MatcherOptions) => HTMLElement[];
143
findByAltText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
144
findAllByAltText: (text: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
145
146
getByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement;
147
getAllByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement[];
148
queryByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
149
queryAllByTitle: (title: string | RegExp, options?: MatcherOptions) => HTMLElement[];
150
findByTitle: (title: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
151
findAllByTitle: (title: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
152
153
getByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement;
154
getAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement[];
155
queryByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement | null;
156
queryAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => HTMLElement[];
157
findByTestId: (testId: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement>;
158
findAllByTestId: (testId: string | RegExp, options?: MatcherOptions) => Promise<HTMLElement[]>;
159
}
160
```
161
162
## Common Patterns
163
164
### Basic Rendering
165
```typescript
166
const { container, getByRole, rerender } = render(<Counter initial={0} />);
167
168
expect(getByRole('button')).toBeInTheDocument();
169
170
// Re-render with new props
171
rerender(<Counter initial={5} />);
172
```
173
174
### With Providers
175
```typescript
176
const wrapper = ({ children }) => (
177
<ThemeProvider theme="dark">
178
<AuthProvider user={mockUser}>
179
{children}
180
</AuthProvider>
181
</ThemeProvider>
182
);
183
184
render(<App />, { wrapper });
185
```
186
187
### Custom Container
188
```typescript
189
// For testing <tr>, <td>, etc.
190
const table = document.createElement('table');
191
const tbody = document.createElement('tbody');
192
table.appendChild(tbody);
193
194
render(<TableRow />, { container: tbody });
195
```
196
197
### Hydration (SSR)
198
```typescript
199
const container = document.getElementById('root');
200
container.innerHTML = serverRenderedHTML;
201
202
render(<App />, { container, hydrate: true });
203
```
204
205
### StrictMode
206
```typescript
207
render(<App />, { reactStrictMode: true });
208
```
209
210
## Testing Patterns
211
212
### Testing Re-renders
213
```typescript
214
test('updates on prop change', () => {
215
const { rerender, getByText } = render(<Display value={1} />);
216
expect(getByText('1')).toBeInTheDocument();
217
218
rerender(<Display value={2} />);
219
expect(getByText('2')).toBeInTheDocument();
220
});
221
```
222
223
### Testing Unmount/Cleanup
224
```typescript
225
test('cleans up on unmount', () => {
226
const cleanup = jest.fn();
227
228
function Component() {
229
useEffect(() => cleanup, []);
230
return <div>Content</div>;
231
}
232
233
const { unmount } = render(<Component />);
234
unmount();
235
236
expect(cleanup).toHaveBeenCalled();
237
});
238
```
239
240
### Snapshot Testing
241
```typescript
242
test('matches snapshot', () => {
243
const { asFragment } = render(<App />);
244
expect(asFragment()).toMatchSnapshot();
245
});
246
```
247
248
### Debug Utilities
249
```typescript
250
const { debug, getByRole } = render(<App />);
251
252
debug(); // Print entire DOM
253
debug(getByRole('button')); // Print specific element
254
debug(getByRole('button'), 1000); // Limit output length
255
```
256
257
## Query Access
258
259
All queries available three ways:
260
261
```typescript
262
// 1. From render result
263
const { getByRole } = render(<Component />);
264
const button = getByRole('button');
265
266
// 2. From screen (recommended)
267
import { screen } from '@testing-library/react';
268
render(<Component />);
269
const button = screen.getByRole('button');
270
271
// 3. From within (scoped)
272
const modal = screen.getByRole('dialog');
273
const button = within(modal).getByRole('button');
274
```
275
276
## Render Modes
277
278
### Concurrent (Default React 18+)
279
```typescript
280
render(<App />); // Uses ReactDOMClient.createRoot()
281
```
282
283
### Legacy (React 18 only)
284
```typescript
285
render(<App />, { legacyRoot: true }); // Error in React 19+
286
```
287
288
### Hydration
289
```typescript
290
render(<App />, { hydrate: true }); // Uses hydrateRoot()
291
```
292
293
## Production Setup
294
295
### Custom Render Utility
296
```typescript
297
// test-utils.tsx
298
import { render, RenderOptions } from '@testing-library/react';
299
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
300
import { BrowserRouter } from 'react-router-dom';
301
302
const queryClient = new QueryClient({
303
defaultOptions: { queries: { retry: false } }
304
});
305
306
export function renderWithProviders(
307
ui: React.ReactElement,
308
options?: Omit<RenderOptions, 'wrapper'>
309
) {
310
return render(ui, {
311
wrapper: ({ children }) => (
312
<BrowserRouter>
313
<QueryClientProvider client={queryClient}>
314
{children}
315
</QueryClientProvider>
316
</BrowserRouter>
317
),
318
...options,
319
});
320
}
321
322
// Use in tests
323
import { renderWithProviders } from './test-utils';
324
325
test('renders with providers', () => {
326
renderWithProviders(<MyComponent />);
327
});
328
```
329
330
## Additional Types
331
332
```typescript { .api }
333
type RendererableContainer = Element | DocumentFragment;
334
335
interface MatcherOptions {
336
exact?: boolean;
337
normalizer?: (text: string) => string;
338
}
339
340
interface SelectorMatcherOptions extends MatcherOptions {
341
selector?: string;
342
}
343
344
interface ByRoleOptions extends MatcherOptions {
345
name?: string | RegExp;
346
description?: string | RegExp;
347
hidden?: boolean;
348
selected?: boolean;
349
checked?: boolean;
350
pressed?: boolean;
351
current?: boolean | string;
352
expanded?: boolean;
353
level?: number;
354
queryFallbacks?: boolean;
355
}
356
357
interface Queries {
358
[key: string]: (...args: any[]) => any;
359
}
360
```
361
362
## Deprecated Types
363
364
The following type aliases are deprecated and provided for backward compatibility:
365
366
```typescript { .api }
367
/** @deprecated Use RenderOptions instead */
368
type BaseRenderOptions<Q, Container, BaseElement> = RenderOptions;
369
370
/** @deprecated Use RenderOptions with hydrate: false instead */
371
interface ClientRenderOptions<Q, Container, BaseElement> extends RenderOptions {
372
hydrate?: false | undefined;
373
}
374
375
/** @deprecated Use RenderOptions with hydrate: true instead */
376
interface HydrateOptions<Q, Container, BaseElement> extends RenderOptions {
377
hydrate: true;
378
}
379
```
380
381
## Key Notes
382
383
- Auto-cleanup runs after each test (unless using /pure import)
384
- All operations automatically wrapped in act()
385
- Use screen queries for better error messages
386
- Prefer getByRole for accessibility testing
387
- legacyRoot option only works in React 18, throws error in React 19+
388
- onCaughtError callback only available in React 19+
389