0
# Error Handling
1
2
Robust error and exception handling capabilities for managing uncaught errors, manual failure reporting, and debugging during test execution.
3
4
## Capabilities
5
6
### Uncaught Error Handling
7
8
Handle and report uncaught errors that occur during test execution.
9
10
```javascript { .api }
11
/**
12
* Register callback for uncaught window errors (browser environment)
13
* @param {Function} callback - Function to handle window errors
14
*/
15
QUnit.onError(callback)
16
17
/**
18
* Register callback for uncaught exceptions (Node.js and modern browsers)
19
* @param {Function} callback - Function to handle uncaught exceptions
20
*/
21
QUnit.onUncaughtException(callback)
22
```
23
24
**Usage Examples:**
25
26
```javascript
27
import QUnit from "qunit";
28
29
// Handle uncaught window errors in browser
30
QUnit.onError(function(errorEvent) {
31
console.error("Uncaught window error:", {
32
message: errorEvent.message,
33
filename: errorEvent.filename,
34
lineno: errorEvent.lineno,
35
colno: errorEvent.colno,
36
error: errorEvent.error
37
});
38
39
// Return true to prevent default browser error handling
40
return true;
41
});
42
43
// Handle uncaught exceptions (promises, async functions, etc.)
44
QUnit.onUncaughtException(function(error) {
45
console.error("Uncaught exception:", {
46
name: error.name,
47
message: error.message,
48
stack: error.stack
49
});
50
51
// Log additional context if available
52
if (QUnit.config.current) {
53
console.error("Error occurred in test:", QUnit.config.current.testName);
54
}
55
});
56
57
// Example test that might generate uncaught errors
58
QUnit.test("async error handling", function(assert) {
59
const done = assert.async();
60
61
// This promise rejection might be uncaught
62
Promise.resolve().then(() => {
63
throw new Error("Simulated async error");
64
});
65
66
setTimeout(() => {
67
assert.ok(true, "test completed");
68
done();
69
}, 100);
70
});
71
```
72
73
### Manual Failure Reporting
74
75
Manually report test failures and push custom failure messages.
76
77
```javascript { .api }
78
/**
79
* Manually record a test failure with custom message and source location
80
* @param message - Failure message
81
* @param source - Source location information (optional)
82
*/
83
function pushFailure(message: string, source?: string): void;
84
```
85
86
**Usage Examples:**
87
88
```javascript
89
QUnit.test("manual failure reporting", function(assert) {
90
const user = getCurrentUser();
91
92
if (!user) {
93
// Report failure when user is not available
94
QUnit.pushFailure("Test requires authenticated user but none found");
95
return;
96
}
97
98
// Test continues if user is available
99
assert.ok(user.id, "user has ID");
100
});
101
102
// Helper function for conditional testing
103
function requireFeature(featureName, callback) {
104
if (!isFeatureEnabled(featureName)) {
105
QUnit.pushFailure(
106
`Test requires feature '${featureName}' to be enabled`,
107
QUnit.stack(1) // Include stack trace from caller
108
);
109
return;
110
}
111
112
callback();
113
}
114
115
QUnit.test("feature-dependent test", function(assert) {
116
requireFeature("advanced-search", () => {
117
const results = performAdvancedSearch("query");
118
assert.ok(results.length > 0, "search returns results");
119
});
120
});
121
```
122
123
### Error Context and Debugging
124
125
Enhanced error reporting with context information and debugging utilities.
126
127
**Usage Examples:**
128
129
```javascript
130
// Enhanced error reporting with context
131
function createTestErrorHandler() {
132
const testContext = {
133
browser: navigator?.userAgent || "Node.js",
134
url: window?.location?.href || "N/A",
135
timestamp: new Date().toISOString()
136
};
137
138
QUnit.onError(function(errorEvent) {
139
const errorInfo = {
140
...testContext,
141
error: {
142
message: errorEvent.message,
143
filename: errorEvent.filename,
144
line: errorEvent.lineno,
145
column: errorEvent.colno,
146
stack: errorEvent.error?.stack
147
},
148
test: QUnit.config.current ? {
149
name: QUnit.config.current.testName,
150
module: QUnit.config.current.module.name
151
} : null
152
};
153
154
// Send to error tracking service
155
console.error("Test execution error:", JSON.stringify(errorInfo, null, 2));
156
157
// Report as test failure
158
QUnit.pushFailure(
159
`Uncaught error in test: ${errorEvent.message}`,
160
errorEvent.error?.stack
161
);
162
});
163
164
QUnit.onUncaughtException(function(error) {
165
const errorInfo = {
166
...testContext,
167
error: {
168
name: error.name,
169
message: error.message,
170
stack: error.stack
171
},
172
test: QUnit.config.current ? {
173
name: QUnit.config.current.testName,
174
module: QUnit.config.current.module.name
175
} : null
176
};
177
178
console.error("Uncaught exception in test:", JSON.stringify(errorInfo, null, 2));
179
180
QUnit.pushFailure(
181
`Uncaught exception: ${error.name}: ${error.message}`,
182
error.stack
183
);
184
});
185
}
186
187
// Initialize error handling
188
createTestErrorHandler();
189
```
190
191
### Test-Specific Error Handling
192
193
Handle errors within specific tests or modules.
194
195
**Usage Examples:**
196
197
```javascript
198
QUnit.module("Error Handling Tests", {
199
beforeEach: function() {
200
// Set up test-specific error tracking
201
this.errors = [];
202
203
this.originalOnError = QUnit.onError;
204
QUnit.onError = (error) => {
205
this.errors.push(error);
206
return true; // Prevent default handling
207
};
208
},
209
210
afterEach: function() {
211
// Restore original error handler
212
QUnit.onError = this.originalOnError;
213
}
214
}, function() {
215
216
QUnit.test("capture and verify errors", function(assert) {
217
// Intentionally trigger an error
218
setTimeout(() => {
219
throw new Error("Test error");
220
}, 10);
221
222
const done = assert.async();
223
224
setTimeout(() => {
225
assert.strictEqual(this.errors.length, 1, "one error was captured");
226
assert.strictEqual(this.errors[0].message, "Test error", "correct error message");
227
done();
228
}, 50);
229
});
230
});
231
```
232
233
### Error Recovery and Resilience
234
235
Implement error recovery patterns for robust test suites.
236
237
**Usage Examples:**
238
239
```javascript
240
// Retry mechanism for flaky tests
241
function retryOnError(testFn, maxRetries = 3) {
242
return function(assert) {
243
let attempt = 0;
244
let lastError = null;
245
246
const runTest = () => {
247
attempt++;
248
249
try {
250
const result = testFn.call(this, assert);
251
252
// Handle async tests
253
if (result && typeof result.catch === 'function') {
254
return result.catch(error => {
255
lastError = error;
256
if (attempt < maxRetries) {
257
console.warn(`Test attempt ${attempt} failed, retrying...`);
258
return runTest();
259
} else {
260
QUnit.pushFailure(
261
`Test failed after ${maxRetries} attempts. Last error: ${error.message}`,
262
error.stack
263
);
264
}
265
});
266
}
267
268
return result;
269
} catch (error) {
270
lastError = error;
271
if (attempt < maxRetries) {
272
console.warn(`Test attempt ${attempt} failed, retrying...`);
273
return runTest();
274
} else {
275
QUnit.pushFailure(
276
`Test failed after ${maxRetries} attempts. Last error: ${error.message}`,
277
error.stack
278
);
279
}
280
}
281
};
282
283
return runTest();
284
};
285
}
286
287
QUnit.test("flaky network test", retryOnError(async function(assert) {
288
const response = await fetch("/api/unstable-endpoint");
289
assert.strictEqual(response.status, 200, "API responds successfully");
290
}));
291
292
// Circuit breaker pattern for external dependencies
293
class TestCircuitBreaker {
294
constructor(threshold = 3, timeout = 30000) {
295
this.failureCount = 0;
296
this.threshold = threshold;
297
this.timeout = timeout;
298
this.nextAttempt = Date.now();
299
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
300
}
301
302
async execute(testFn) {
303
if (this.state === 'OPEN') {
304
if (Date.now() < this.nextAttempt) {
305
QUnit.pushFailure("Circuit breaker is OPEN - skipping test");
306
return;
307
} else {
308
this.state = 'HALF_OPEN';
309
}
310
}
311
312
try {
313
const result = await testFn();
314
this.onSuccess();
315
return result;
316
} catch (error) {
317
this.onFailure();
318
throw error;
319
}
320
}
321
322
onSuccess() {
323
this.failureCount = 0;
324
this.state = 'CLOSED';
325
}
326
327
onFailure() {
328
this.failureCount++;
329
if (this.failureCount >= this.threshold) {
330
this.state = 'OPEN';
331
this.nextAttempt = Date.now() + this.timeout;
332
}
333
}
334
}
335
336
const externalServiceBreaker = new TestCircuitBreaker();
337
338
QUnit.test("external service test", async function(assert) {
339
await externalServiceBreaker.execute(async () => {
340
const result = await callExternalService();
341
assert.ok(result.success, "external service responded");
342
});
343
});
344
```
345
346
### Legacy Error Handling
347
348
Support for deprecated error handling methods.
349
350
```javascript { .api }
351
/**
352
* @deprecated Use QUnit.onUncaughtException instead
353
* Handle unhandled promise rejections
354
* @param reason - Rejection reason
355
*/
356
function onUnhandledRejection(reason: any): void;
357
```
358
359
**Usage Examples:**
360
361
```javascript
362
// Legacy method (deprecated but still available)
363
QUnit.onUnhandledRejection(function(reason) {
364
console.warn("QUnit.onUnhandledRejection is deprecated");
365
console.error("Unhandled rejection:", reason);
366
});
367
368
// Modern equivalent
369
QUnit.onUncaughtException(function(error) {
370
console.error("Uncaught exception:", error);
371
});
372
```
373
374
## Types
375
376
```javascript { .api }
377
interface ErrorHandler {
378
(error: Error): void;
379
}
380
381
interface WindowErrorHandler {
382
(errorEvent: ErrorEvent): boolean | void;
383
}
384
385
interface ErrorEvent {
386
/** Error message */
387
message: string;
388
/** Source filename */
389
filename: string;
390
/** Line number */
391
lineno: number;
392
/** Column number */
393
colno: number;
394
/** Error object */
395
error: Error;
396
}
397
398
interface TestError {
399
/** Error name */
400
name: string;
401
/** Error message */
402
message: string;
403
/** Stack trace */
404
stack: string;
405
/** Source location */
406
source?: string;
407
/** Test context when error occurred */
408
testName?: string;
409
/** Module context when error occurred */
410
module?: string;
411
}
412
413
interface ErrorContext {
414
/** Current test information */
415
test?: {
416
name: string;
417
module: string;
418
id: string;
419
};
420
/** Browser/environment information */
421
environment?: {
422
userAgent: string;
423
url: string;
424
timestamp: string;
425
};
426
/** Custom error data */
427
[key: string]: any;
428
}
429
```