0
# Cleanup Management
1
2
Cleanup utilities for managing test teardown, both automatic and manual cleanup of rendered hooks and associated resources. Proper cleanup prevents memory leaks and ensures test isolation.
3
4
## Capabilities
5
6
### cleanup Function
7
8
Runs all registered cleanup callbacks and clears the cleanup registry. This is typically called automatically after each test, but can be called manually when needed.
9
10
```typescript { .api }
11
/**
12
* Run all registered cleanup callbacks and clear the registry
13
* @returns Promise that resolves when all cleanup is complete
14
*/
15
function cleanup(): Promise<void>;
16
```
17
18
**Usage Examples:**
19
20
```typescript
21
import { renderHook, cleanup } from "@testing-library/react-hooks";
22
23
function useTimer() {
24
const [count, setCount] = useState(0);
25
26
useEffect(() => {
27
const interval = setInterval(() => {
28
setCount(prev => prev + 1);
29
}, 1000);
30
31
return () => clearInterval(interval);
32
}, []);
33
34
return count;
35
}
36
37
test("manual cleanup", async () => {
38
const { result } = renderHook(() => useTimer());
39
40
expect(result.current).toBe(0);
41
42
// Manually trigger cleanup
43
await cleanup();
44
45
// Hook should be unmounted and timers cleared
46
});
47
48
// Multiple hooks in same test
49
test("cleanup multiple hooks", async () => {
50
const { result: result1 } = renderHook(() => useTimer());
51
const { result: result2 } = renderHook(() => useTimer());
52
53
expect(result1.current).toBe(0);
54
expect(result2.current).toBe(0);
55
56
// Cleanup all rendered hooks
57
await cleanup();
58
});
59
```
60
61
### addCleanup Function
62
63
Registers a custom cleanup callback that will be called during cleanup. Returns a function to remove the callback.
64
65
```typescript { .api }
66
/**
67
* Add a custom cleanup callback
68
* @param callback - Function to call during cleanup (can be async)
69
* @returns Function to remove this cleanup callback
70
*/
71
function addCleanup(callback: CleanupCallback): () => void;
72
73
type CleanupCallback = () => Promise<void> | void;
74
```
75
76
**Usage Examples:**
77
78
```typescript
79
import { renderHook, addCleanup } from "@testing-library/react-hooks";
80
81
// Custom resource cleanup
82
test("custom cleanup callback", async () => {
83
const mockResource = {
84
data: "important data",
85
dispose: jest.fn()
86
};
87
88
// Register custom cleanup
89
const removeCleanup = addCleanup(() => {
90
mockResource.dispose();
91
mockResource.data = null;
92
});
93
94
const { result } = renderHook(() => {
95
// Hook that uses the resource
96
return mockResource.data;
97
});
98
99
expect(result.current).toBe("important data");
100
101
// Cleanup will be called automatically after test
102
// or manually with cleanup()
103
});
104
105
// Async cleanup
106
test("async cleanup callback", async () => {
107
const mockDatabase = {
108
connected: true,
109
disconnect: jest.fn().mockResolvedValue(undefined)
110
};
111
112
addCleanup(async () => {
113
await mockDatabase.disconnect();
114
mockDatabase.connected = false;
115
});
116
117
const { result } = renderHook(() => mockDatabase.connected);
118
119
expect(result.current).toBe(true);
120
});
121
122
// Conditional cleanup removal
123
test("remove cleanup callback", () => {
124
const cleanupFn = jest.fn();
125
126
const removeCleanup = addCleanup(cleanupFn);
127
128
// Later, if cleanup is no longer needed
129
removeCleanup();
130
131
// cleanupFn will not be called during cleanup
132
});
133
```
134
135
### removeCleanup Function
136
137
Removes a previously registered cleanup callback from the cleanup registry.
138
139
```typescript { .api }
140
/**
141
* Remove a previously registered cleanup callback
142
* @param callback - The cleanup callback to remove
143
*/
144
function removeCleanup(callback: CleanupCallback): void;
145
```
146
147
**Usage Examples:**
148
149
```typescript
150
import { renderHook, addCleanup, removeCleanup } from "@testing-library/react-hooks";
151
152
test("manual cleanup removal", () => {
153
const cleanupCallback = jest.fn();
154
155
// Add cleanup
156
addCleanup(cleanupCallback);
157
158
// Later, remove it
159
removeCleanup(cleanupCallback);
160
161
// Callback will not be called during cleanup
162
});
163
164
// Cleanup lifecycle management
165
function useResourceWithCleanup(shouldCleanup: boolean) {
166
const [resource] = useState(() => createResource());
167
168
useEffect(() => {
169
if (shouldCleanup) {
170
const cleanup = () => resource.dispose();
171
addCleanup(cleanup);
172
173
return () => removeCleanup(cleanup);
174
}
175
}, [shouldCleanup, resource]);
176
177
return resource;
178
}
179
180
test("conditional cleanup registration", () => {
181
const { rerender } = renderHook(
182
({ shouldCleanup }) => useResourceWithCleanup(shouldCleanup),
183
{ initialProps: { shouldCleanup: false } }
184
);
185
186
// Initially no cleanup registered
187
188
// Enable cleanup
189
rerender({ shouldCleanup: true });
190
191
// Cleanup is now registered
192
193
// Disable cleanup
194
rerender({ shouldCleanup: false });
195
196
// Cleanup is removed
197
});
198
```
199
200
### Automatic Cleanup
201
202
The library automatically registers cleanup for all rendered hooks. This happens through the auto-cleanup system:
203
204
```typescript
205
// Automatic cleanup is enabled by default
206
import { renderHook } from "@testing-library/react-hooks";
207
208
// Each renderHook call automatically registers cleanup
209
test("automatic cleanup", () => {
210
const { result, unmount } = renderHook(() => useState(0));
211
212
// Hook will be automatically cleaned up after test
213
// No manual cleanup needed
214
});
215
216
// Hooks are also cleaned up when explicitly unmounted
217
test("explicit unmount", () => {
218
const { result, unmount } = renderHook(() => useState(0));
219
220
expect(result.current[0]).toBe(0);
221
222
// Explicitly unmount (also removes from cleanup registry)
223
unmount();
224
225
// Hook is now unmounted and cleaned up
226
});
227
```
228
229
### Disabling Auto-Cleanup
230
231
You can disable automatic cleanup by importing a special configuration file:
232
233
```javascript
234
// At the top of your test file or in setup
235
import "@testing-library/react-hooks/dont-cleanup-after-each";
236
237
// Or require in CommonJS
238
require("@testing-library/react-hooks/dont-cleanup-after-each");
239
```
240
241
**Usage with Manual Cleanup:**
242
243
```typescript
244
// After importing dont-cleanup-after-each
245
import { renderHook, cleanup } from "@testing-library/react-hooks";
246
247
describe("manual cleanup tests", () => {
248
afterEach(async () => {
249
// Manually call cleanup after each test
250
await cleanup();
251
});
252
253
test("hook test 1", () => {
254
const { result } = renderHook(() => useState(0));
255
// Test logic...
256
});
257
258
test("hook test 2", () => {
259
const { result } = renderHook(() => useState(1));
260
// Test logic...
261
});
262
});
263
```
264
265
### Cleanup Error Handling
266
267
Cleanup callbacks can handle errors gracefully:
268
269
```typescript
270
test("cleanup error handling", async () => {
271
const failingCleanup = jest.fn(() => {
272
throw new Error("Cleanup failed");
273
});
274
275
const successfulCleanup = jest.fn();
276
277
addCleanup(failingCleanup);
278
addCleanup(successfulCleanup);
279
280
// Cleanup continues even if some callbacks fail
281
await cleanup();
282
283
expect(failingCleanup).toHaveBeenCalled();
284
expect(successfulCleanup).toHaveBeenCalled();
285
});
286
287
// Async cleanup errors
288
test("async cleanup error handling", async () => {
289
const failingAsyncCleanup = jest.fn().mockRejectedValue(
290
new Error("Async cleanup failed")
291
);
292
293
const successfulCleanup = jest.fn();
294
295
addCleanup(failingAsyncCleanup);
296
addCleanup(successfulCleanup);
297
298
// All cleanup callbacks run despite errors
299
await cleanup();
300
301
expect(failingAsyncCleanup).toHaveBeenCalled();
302
expect(successfulCleanup).toHaveBeenCalled();
303
});
304
```
305
306
### Best Practices
307
308
**Resource Management:**
309
```typescript
310
function useFileResource(filename: string) {
311
const [file, setFile] = useState(null);
312
313
useEffect(() => {
314
const fileHandle = openFile(filename);
315
setFile(fileHandle);
316
317
// Register cleanup for the file handle
318
const cleanup = () => fileHandle.close();
319
addCleanup(cleanup);
320
321
return () => {
322
fileHandle.close();
323
removeCleanup(cleanup);
324
};
325
}, [filename]);
326
327
return file;
328
}
329
```
330
331
**Test Isolation:**
332
```typescript
333
describe("test suite with shared resources", () => {
334
let sharedResource;
335
336
beforeEach(() => {
337
sharedResource = createSharedResource();
338
339
// Register cleanup for shared resource
340
addCleanup(() => {
341
sharedResource.dispose();
342
sharedResource = null;
343
});
344
});
345
346
test("test 1", () => {
347
const { result } = renderHook(() => useSharedResource(sharedResource));
348
// Test logic...
349
});
350
351
test("test 2", () => {
352
const { result } = renderHook(() => useSharedResource(sharedResource));
353
// Test logic...
354
});
355
});
356
```