0
# Lifecycle Hooks
1
2
Before and after hooks for test setup and teardown at multiple levels, providing comprehensive test lifecycle management.
3
4
## Capabilities
5
6
### Test-Level Hooks
7
8
Hooks that run before and after individual tests, useful for test-specific setup and cleanup.
9
10
```typescript { .api }
11
/**
12
* Run a function before the current test starts
13
* @param fn - Function to run before test execution
14
*/
15
function before(fn: () => void | Promise<void>): void;
16
17
/**
18
* Run a function after the current test completes
19
* @param fn - Function to run after test execution
20
*/
21
function after(fn: () => void | Promise<void>): void;
22
```
23
24
**Usage Examples:**
25
26
```typescript
27
import { test, before, after } from "tap";
28
29
test("test with setup and teardown", (t) => {
30
let testData: any;
31
32
before(async () => {
33
// Setup runs before test assertions
34
testData = await setupTestData();
35
console.log("Test setup completed");
36
});
37
38
after(async () => {
39
// Cleanup runs after test completes
40
await cleanupTestData(testData);
41
console.log("Test cleanup completed");
42
});
43
44
t.ok(testData, "test data should be available");
45
t.end();
46
});
47
```
48
49
### Subtest-Level Hooks
50
51
Hooks that run before and after each subtest, perfect for repetitive setup/teardown across multiple related tests.
52
53
```typescript { .api }
54
/**
55
* Run a function before each subtest
56
* @param fn - Function to run before each subtest execution
57
*/
58
function beforeEach(fn: () => void | Promise<void>): void;
59
60
/**
61
* Run a function after each subtest completes
62
* @param fn - Function to run after each subtest execution
63
*/
64
function afterEach(fn: () => void | Promise<void>): void;
65
```
66
67
**Usage Examples:**
68
69
```typescript
70
test("parent test with subtest hooks", (t) => {
71
let sharedResource: any;
72
73
beforeEach(async () => {
74
// Runs before each subtest
75
sharedResource = await createResource();
76
console.log("Subtest setup completed");
77
});
78
79
afterEach(async () => {
80
// Runs after each subtest
81
await cleanupResource(sharedResource);
82
console.log("Subtest cleanup completed");
83
});
84
85
t.test("first subtest", (st) => {
86
st.ok(sharedResource, "resource available");
87
st.end();
88
});
89
90
t.test("second subtest", (st) => {
91
st.ok(sharedResource, "resource available");
92
st.end();
93
});
94
95
t.end();
96
});
97
```
98
99
### Hook Execution Order
100
101
Hooks execute in a predictable order relative to test execution:
102
103
1. `before` hooks (outermost to innermost)
104
2. Test execution begins
105
3. `beforeEach` hooks (for subtests)
106
4. Subtest execution
107
5. `afterEach` hooks (for subtests)
108
6. `after` hooks (innermost to outermost)
109
110
```typescript
111
test("hook execution order", (t) => {
112
before(() => console.log("1. Parent before"));
113
114
beforeEach(() => console.log("2. Parent beforeEach"));
115
116
after(() => console.log("6. Parent after"));
117
118
afterEach(() => console.log("5. Parent afterEach"));
119
120
t.test("subtest", (st) => {
121
before(() => console.log("3. Subtest before"));
122
123
after(() => console.log("4. Subtest after"));
124
125
st.ok(true, "subtest assertion");
126
st.end();
127
});
128
129
t.end();
130
});
131
```
132
133
### Async Hook Patterns
134
135
Hooks support both synchronous and asynchronous execution:
136
137
```typescript
138
test("async hooks", (t) => {
139
before(async () => {
140
// Async setup
141
await new Promise(resolve => setTimeout(resolve, 100));
142
console.log("Async setup completed");
143
});
144
145
after(() => {
146
// Sync cleanup
147
console.log("Sync cleanup completed");
148
});
149
150
beforeEach(async () => {
151
// Each subtest gets fresh async setup
152
await initializeSubtestData();
153
});
154
155
t.test("async subtest", async (st) => {
156
const result = await performAsyncOperation();
157
st.ok(result.success);
158
});
159
160
t.end();
161
});
162
```
163
164
### Error Handling in Hooks
165
166
Errors in hooks will cause the test to fail:
167
168
```typescript
169
test("hook error handling", (t) => {
170
before(() => {
171
throw new Error("Setup failed");
172
// This will cause the test to fail before any assertions run
173
});
174
175
after(() => {
176
// This still runs even if test or other hooks fail
177
console.log("Cleanup always runs");
178
});
179
180
t.ok(true, "This assertion might not run if before hook fails");
181
t.end();
182
});
183
```
184
185
### Resource Management Patterns
186
187
Common patterns for managing resources with hooks:
188
189
```typescript
190
// Database connection pattern
191
test("database tests", (t) => {
192
let db: Database;
193
194
before(async () => {
195
db = await Database.connect(testConnectionString);
196
});
197
198
after(async () => {
199
if (db) {
200
await db.close();
201
}
202
});
203
204
beforeEach(async () => {
205
// Clean slate for each subtest
206
await db.truncateAll();
207
await db.seedTestData();
208
});
209
210
t.test("user operations", async (st) => {
211
const user = await db.createUser({ name: "test" });
212
st.ok(user.id);
213
});
214
215
t.test("product operations", async (st) => {
216
const product = await db.createProduct({ name: "widget" });
217
st.ok(product.id);
218
});
219
220
t.end();
221
});
222
223
// File system pattern
224
test("file operations", (t) => {
225
let tempDir: string;
226
227
before(async () => {
228
tempDir = await fs.mkdtemp("/tmp/test-");
229
});
230
231
after(async () => {
232
await fs.rmdir(tempDir, { recursive: true });
233
});
234
235
t.test("create file", async (st) => {
236
const filePath = path.join(tempDir, "test.txt");
237
await fs.writeFile(filePath, "test content");
238
st.ok(await fs.pathExists(filePath));
239
});
240
241
t.end();
242
});
243
244
// HTTP server pattern
245
test("API tests", (t) => {
246
let server: Server;
247
let baseUrl: string;
248
249
before(async () => {
250
server = createTestServer();
251
await server.listen(0); // Random port
252
baseUrl = `http://localhost:${server.address().port}`;
253
});
254
255
after(async () => {
256
await server.close();
257
});
258
259
beforeEach(() => {
260
// Reset server state between tests
261
server.resetMocks();
262
});
263
264
t.test("GET /users", async (st) => {
265
const response = await fetch(`${baseUrl}/users`);
266
st.equal(response.status, 200);
267
});
268
269
t.end();
270
});
271
```
272
273
### Hook Scope and Inheritance
274
275
Hooks are scoped to their containing test and inherited by subtests:
276
277
```typescript
278
test("parent", (t) => {
279
before(() => console.log("Parent before"));
280
beforeEach(() => console.log("Parent beforeEach"));
281
282
t.test("child", (st) => {
283
// Inherits parent hooks
284
before(() => console.log("Child before"));
285
286
st.test("grandchild", (gst) => {
287
// Inherits both parent and child hooks
288
gst.ok(true);
289
gst.end();
290
});
291
292
st.end();
293
});
294
295
t.end();
296
});
297
```
298
299
### Multiple Hooks
300
301
You can register multiple hooks of the same type:
302
303
```typescript
304
test("multiple hooks", (t) => {
305
before(() => console.log("First before"));
306
before(() => console.log("Second before"));
307
308
after(() => console.log("First after"));
309
after(() => console.log("Second after"));
310
311
// All hooks run in registration order
312
t.ok(true);
313
t.end();
314
});
315
```
316
317
### Conditional Hooks
318
319
Hooks can be conditionally registered:
320
321
```typescript
322
test("conditional setup", (t) => {
323
if (process.env.ENABLE_SLOW_SETUP) {
324
before(async () => {
325
await performExpensiveSetup();
326
});
327
}
328
329
if (needsCleanup) {
330
after(() => {
331
performCleanup();
332
});
333
}
334
335
t.ok(true);
336
t.end();
337
});
338
```