0
# Error Handling
1
2
Enhanced error handling with custom stack trace management for better test debugging. The ErrorWithStack class provides improved error reporting by excluding specific functions from stack traces and optimizing stack trace capture.
3
4
## Capabilities
5
6
### ErrorWithStack Class
7
8
Error class that captures stack traces while excluding a specific callsite function, providing cleaner and more useful stack traces for debugging.
9
10
```typescript { .api }
11
/**
12
* Error class with custom stack trace callsite exclusion
13
*/
14
class ErrorWithStack extends Error {
15
/**
16
* Creates an Error with enhanced stack trace handling
17
* @param message - Error message (can be undefined)
18
* @param callsite - Function to exclude from stack trace
19
* @param stackLimit - Optional stack trace limit for performance
20
*/
21
constructor(
22
message: string | undefined,
23
callsite: (...args: Array<any>) => unknown,
24
stackLimit?: number
25
);
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { ErrorWithStack } from "jest-util";
33
34
// Basic usage - exclude current function from stack
35
function validateTestInput(input: any) {
36
if (input == null) {
37
throw new ErrorWithStack("Input cannot be null", validateTestInput);
38
}
39
}
40
41
// The stack trace will start from the caller of validateTestInput,
42
// not from within validateTestInput itself
43
44
// Jest assertion helper
45
function expectToBeTruthy(value: any, message?: string) {
46
if (!value) {
47
throw new ErrorWithStack(
48
message || `Expected ${value} to be truthy`,
49
expectToBeTruthy
50
);
51
}
52
}
53
54
// Usage in test
55
test("should validate user input", () => {
56
const userData = null;
57
expectToBeTruthy(userData, "User data is required");
58
// Stack trace points to this line, not inside expectToBeTruthy
59
});
60
61
// Custom test framework utilities
62
function createAssertion(name: string, predicate: (value: any) => boolean) {
63
return function assertion(value: any) {
64
if (!predicate(value)) {
65
throw new ErrorWithStack(
66
`Assertion failed: ${name}`,
67
assertion
68
);
69
}
70
};
71
}
72
73
const shouldBePositive = createAssertion("should be positive", x => x > 0);
74
shouldBePositive(-5); // Stack trace excludes the assertion function
75
76
// Performance optimization with stack limit
77
function createPerformantError(message: string, callsite: Function) {
78
return new ErrorWithStack(message, callsite, 10); // Limit stack to 10 frames
79
}
80
```
81
82
**Advanced Usage:**
83
84
```typescript
85
// Test helper with multiple levels
86
function deepTestHelper(value: any) {
87
return intermediateHelper(value);
88
}
89
90
function intermediateHelper(value: any) {
91
if (typeof value !== "string") {
92
// Exclude the original calling function, not intermediate ones
93
throw new ErrorWithStack("Expected string", deepTestHelper);
94
}
95
}
96
97
// Custom error factory
98
class ValidationError extends ErrorWithStack {
99
constructor(field: string, value: any, validator: Function) {
100
super(
101
`Validation failed for field '${field}' with value: ${value}`,
102
validator
103
);
104
this.name = "ValidationError";
105
}
106
}
107
108
function validateEmail(email: string) {
109
if (!email.includes("@")) {
110
throw new ValidationError("email", email, validateEmail);
111
}
112
}
113
114
// Error aggregation for test suites
115
function collectTestErrors(tests: Array<() => void>): ErrorWithStack[] {
116
const errors: ErrorWithStack[] = [];
117
118
tests.forEach((test, index) => {
119
try {
120
test();
121
} catch (error) {
122
if (error instanceof ErrorWithStack) {
123
errors.push(error);
124
} else {
125
errors.push(new ErrorWithStack(
126
`Test ${index} failed: ${error.message}`,
127
collectTestErrors
128
));
129
}
130
}
131
});
132
133
return errors;
134
}
135
```
136
137
**Stack Trace Behavior:**
138
139
```typescript
140
// Without ErrorWithStack
141
function regularError() {
142
throw new Error("Regular error");
143
}
144
145
function callRegularError() {
146
regularError(); // This will show in stack trace
147
}
148
149
// Stack trace shows:
150
// Error: Regular error
151
// at regularError (file.js:2:9)
152
// at callRegularError (file.js:6:3)
153
// at ...
154
155
// With ErrorWithStack
156
function betterError() {
157
throw new ErrorWithStack("Better error", betterError);
158
}
159
160
function callBetterError() {
161
betterError(); // ErrorWithStack excludes betterError from trace
162
}
163
164
// Stack trace shows:
165
// ErrorWithStack: Better error
166
// at callBetterError (file.js:16:3)
167
// at ...
168
// Notice betterError function is excluded
169
```
170
171
**Performance Features:**
172
173
- **Stack Limit Control**: Optional `stackLimit` parameter prevents excessive stack capture
174
- **Efficient Capture**: Uses `Error.captureStackTrace()` when available (V8 engines)
175
- **Temporary Limit Adjustment**: Temporarily increases `Error.stackTraceLimit` during capture for better traces
176
- **Memory Optimization**: Limits stack depth to prevent memory issues in deep call stacks
177
178
**Integration with Testing Frameworks:**
179
180
```typescript
181
// Jest custom matcher
182
expect.extend({
183
toBeValidUser(received) {
184
if (!received || !received.id || !received.name) {
185
throw new ErrorWithStack(
186
`Expected valid user object, received: ${JSON.stringify(received)}`,
187
this.toBeValidUser
188
);
189
}
190
return { pass: true, message: () => "User is valid" };
191
}
192
});
193
194
// Chai-style assertion
195
function expect(value: any) {
196
return {
197
toBe(expected: any) {
198
if (value !== expected) {
199
throw new ErrorWithStack(
200
`Expected ${value} to be ${expected}`,
201
this.toBe
202
);
203
}
204
}
205
};
206
}
207
```
208
209
## Error Message Best Practices
210
211
When using ErrorWithStack, follow these patterns for clear error messages:
212
213
- **Be Specific**: Include actual and expected values when possible
214
- **Provide Context**: Mention what operation was being performed
215
- **Use Consistent Format**: Follow a standard message pattern across your tests
216
- **Include Debugging Info**: Add relevant details that help identify the issue
217
218
```typescript
219
// Good error messages
220
throw new ErrorWithStack(
221
`Expected array to have length 3, but got length ${arr.length}`,
222
validateArrayLength
223
);
224
225
throw new ErrorWithStack(
226
`User validation failed: missing required field 'email'`,
227
validateUser
228
);
229
230
// Less helpful messages
231
throw new ErrorWithStack("Invalid input", someFunction);
232
throw new ErrorWithStack("Error occurred", someFunction);
233
```