0
# Test Lifecycle Hooks
1
2
Hook functions that run at specific points in the test lifecycle for setup and teardown operations. These hooks provide a clean way to prepare test environments and clean up resources.
3
4
## Capabilities
5
6
### Before Hook
7
8
Runs once before all tests in the current suite. Ideal for expensive setup operations that can be shared across multiple tests.
9
10
```javascript { .api }
11
/**
12
* Runs once before all tests in the current suite
13
* @param fn - Setup function to execute
14
* @param options - Optional hook configuration
15
*/
16
function before(fn: () => void | Promise<void>, options?: { signal?: AbortSignal, timeout?: number }): void;
17
```
18
19
**Usage Examples:**
20
21
```javascript
22
import { describe, it, before } from "test";
23
24
describe("Database tests", () => {
25
before(async () => {
26
// Setup database connection
27
await connectToDatabase();
28
await runMigrations();
29
});
30
31
it("should create user", () => {
32
// Test implementation
33
});
34
35
it("should update user", () => {
36
// Test implementation
37
});
38
});
39
40
// Top-level before hook
41
before(() => {
42
console.log("Starting all tests");
43
process.env.NODE_ENV = "test";
44
});
45
```
46
47
### After Hook
48
49
Runs once after all tests in the current suite have completed. Used for cleanup operations that should happen regardless of test success or failure.
50
51
```javascript { .api }
52
/**
53
* Runs once after all tests in the current suite
54
* @param fn - Cleanup function to execute
55
* @param options - Optional hook configuration
56
*/
57
function after(fn: () => void | Promise<void>, options?: { signal?: AbortSignal, timeout?: number }): void;
58
```
59
60
**Usage Examples:**
61
62
```javascript
63
import { describe, it, before, after } from "test";
64
65
describe("File system tests", () => {
66
before(() => {
67
// Create test directory
68
fs.mkdirSync('./test-files');
69
});
70
71
after(() => {
72
// Clean up test directory
73
fs.rmSync('./test-files', { recursive: true });
74
});
75
76
it("should create file", () => {
77
// Test implementation
78
});
79
});
80
81
// Top-level after hook
82
after(async () => {
83
console.log("All tests completed");
84
await cleanup();
85
});
86
```
87
88
### BeforeEach Hook
89
90
Runs before each individual test. Perfect for ensuring each test starts with a clean, predictable state.
91
92
```javascript { .api }
93
/**
94
* Runs before each individual test
95
* @param fn - Setup function to execute before each test
96
* @param options - Optional hook configuration
97
*/
98
function beforeEach(fn: () => void | Promise<void>, options?: { signal?: AbortSignal, timeout?: number }): void;
99
```
100
101
**Usage Examples:**
102
103
```javascript
104
import { describe, it, beforeEach } from "test";
105
106
describe("Calculator tests", () => {
107
let calculator;
108
109
beforeEach(() => {
110
// Fresh calculator instance for each test
111
calculator = new Calculator();
112
calculator.reset();
113
});
114
115
it("should add numbers", () => {
116
const result = calculator.add(2, 3);
117
if (result !== 5) throw new Error("Addition failed");
118
});
119
120
it("should subtract numbers", () => {
121
const result = calculator.subtract(5, 3);
122
if (result !== 2) throw new Error("Subtraction failed");
123
});
124
});
125
126
// Async beforeEach
127
describe("API tests", () => {
128
beforeEach(async () => {
129
await resetDatabase();
130
await seedTestData();
131
});
132
133
it("should fetch users", async () => {
134
// Test implementation
135
});
136
});
137
```
138
139
### AfterEach Hook
140
141
Runs after each individual test completes. Used for cleaning up test-specific resources and ensuring tests don't interfere with each other.
142
143
```javascript { .api }
144
/**
145
* Runs after each individual test
146
* @param fn - Cleanup function to execute after each test
147
* @param options - Optional hook configuration
148
*/
149
function afterEach(fn: () => void | Promise<void>, options?: { signal?: AbortSignal, timeout?: number }): void;
150
```
151
152
**Usage Examples:**
153
154
```javascript
155
import { describe, it, beforeEach, afterEach } from "test";
156
157
describe("Cache tests", () => {
158
beforeEach(() => {
159
// Initialize cache
160
cache.init();
161
});
162
163
afterEach(() => {
164
// Clear cache after each test
165
cache.clear();
166
});
167
168
it("should store values", () => {
169
cache.set("key", "value");
170
if (cache.get("key") !== "value") {
171
throw new Error("Cache store failed");
172
}
173
});
174
175
it("should handle expiration", async () => {
176
cache.set("key", "value", { ttl: 10 });
177
await new Promise(resolve => setTimeout(resolve, 20));
178
if (cache.get("key") !== null) {
179
throw new Error("Cache expiration failed");
180
}
181
});
182
});
183
184
// Async afterEach
185
describe("Resource tests", () => {
186
afterEach(async () => {
187
await closeConnections();
188
await cleanupTempFiles();
189
});
190
191
it("should manage resources", () => {
192
// Test implementation
193
});
194
});
195
```
196
197
## Hook Execution Order
198
199
Hooks execute in the following order for nested suites:
200
201
1. Outer `before` hooks
202
2. Inner `before` hooks
203
3. For each test:
204
- Outer `beforeEach` hooks
205
- Inner `beforeEach` hooks
206
- **Test execution**
207
- Inner `afterEach` hooks
208
- Outer `afterEach` hooks
209
4. Inner `after` hooks
210
5. Outer `after` hooks
211
212
**Example:**
213
214
```javascript
215
import { describe, it, before, after, beforeEach, afterEach } from "test";
216
217
before(() => console.log("1. Global before"));
218
after(() => console.log("8. Global after"));
219
220
describe("Outer suite", () => {
221
before(() => console.log("2. Outer before"));
222
beforeEach(() => console.log("3. Outer beforeEach"));
223
afterEach(() => console.log("6. Outer afterEach"));
224
after(() => console.log("7. Outer after"));
225
226
describe("Inner suite", () => {
227
before(() => console.log("3. Inner before"));
228
beforeEach(() => console.log("4. Inner beforeEach"));
229
afterEach(() => console.log("5. Inner afterEach"));
230
after(() => console.log("6. Inner after"));
231
232
it("test case", () => {
233
console.log("5. Test execution");
234
});
235
});
236
});
237
```
238
239
## Error Handling in Hooks
240
241
If a hook throws an error or returns a rejected promise:
242
243
- **before/beforeEach errors**: Skip the associated tests
244
- **after/afterEach errors**: Mark tests as failed but continue cleanup
245
- All hooks of the same type continue to run even if one fails
246
247
```javascript
248
describe("Error handling", () => {
249
before(() => {
250
throw new Error("Setup failed");
251
// This will cause all tests in this suite to be skipped
252
});
253
254
afterEach(() => {
255
// This runs even if the test or beforeEach failed
256
cleanup();
257
});
258
259
it("this test will be skipped", () => {
260
// Won't run due to before hook failure
261
});
262
});
263
```
264
265
## Hook Scope
266
267
Hooks only apply to tests within their scope:
268
269
```javascript
270
// Global hooks - apply to all tests
271
before(() => {
272
// Runs before any test
273
});
274
275
describe("Suite A", () => {
276
// Suite-level hooks - only apply to tests in this suite
277
beforeEach(() => {
278
// Only runs before tests in Suite A
279
});
280
281
it("test 1", () => {});
282
it("test 2", () => {});
283
});
284
285
describe("Suite B", () => {
286
// Different suite-level hooks
287
beforeEach(() => {
288
// Only runs before tests in Suite B
289
});
290
291
it("test 3", () => {});
292
});
293
```
294
295
## Best Practices
296
297
1. **Keep hooks simple**: Focus on setup/cleanup, avoid complex logic
298
2. **Use async/await**: For asynchronous operations in hooks
299
3. **Clean up resources**: Always clean up in after/afterEach hooks
300
4. **Fail fast**: If setup fails, let the hook throw an error
301
5. **Scope appropriately**: Use the most specific hook scope possible
302
303
```javascript
304
describe("Best practices example", () => {
305
let server;
306
let client;
307
308
before(async () => {
309
// Expensive setup once per suite
310
server = await startTestServer();
311
});
312
313
beforeEach(() => {
314
// Fresh client for each test
315
client = new ApiClient(server.url);
316
});
317
318
afterEach(() => {
319
// Clean up test-specific resources
320
client.close();
321
});
322
323
after(async () => {
324
// Clean up suite-level resources
325
await server.close();
326
});
327
328
it("should connect", async () => {
329
await client.connect();
330
if (!client.isConnected()) {
331
throw new Error("Connection failed");
332
}
333
});
334
});
335
```