0
# Exception Matchers
1
2
Matchers for testing error conditions and exception handling. These matchers are essential for verifying that functions throw appropriate errors and for testing error handling logic.
3
4
## Capabilities
5
6
### Basic Exception Testing
7
8
#### toThrow
9
10
Tests that a function throws an error when called. Can optionally verify the error message, error type, or error pattern.
11
12
```javascript { .api }
13
/**
14
* Checks if function throws an error, optionally matching type, message, or pattern
15
* @param expected - Optional: Error constructor, error message string, or RegExp pattern
16
*/
17
ExpectationObject.toThrow(expected?: string | Error | RegExp): void;
18
```
19
20
**Usage Examples:**
21
22
```javascript
23
// Test that a function throws any error
24
expect(() => {
25
throw new Error('Something went wrong');
26
}).toThrow();
27
28
// Test that a function throws with specific message
29
expect(() => {
30
throw new Error('Invalid input');
31
}).toThrow('Invalid input');
32
33
// Test with partial message matching
34
expect(() => {
35
throw new Error('Database connection failed: timeout');
36
}).toThrow('Database connection failed');
37
38
// Test with regex pattern
39
expect(() => {
40
throw new Error('User not found: ID 123');
41
}).toThrow(/User not found: ID \d+/);
42
43
// Test with Error constructor
44
expect(() => {
45
throw new TypeError('Expected string');
46
}).toThrow(TypeError);
47
48
// Test with specific Error instance
49
expect(() => {
50
throw new RangeError('Value out of range');
51
}).toThrow(new RangeError('Value out of range'));
52
```
53
54
#### toThrowError
55
56
Alias for `toThrow` - functions identically.
57
58
```javascript { .api }
59
/**
60
* Alias for toThrow - checks if function throws error
61
* @param expected - Optional: Error constructor, error message string, or RegExp pattern
62
*/
63
ExpectationObject.toThrowError(expected?: string | Error | RegExp): void;
64
```
65
66
**Usage Examples:**
67
68
```javascript
69
// All examples from toThrow work identically with toThrowError
70
expect(() => {
71
throw new Error('Something went wrong');
72
}).toThrowError();
73
74
expect(() => {
75
throw new Error('Invalid input');
76
}).toThrowError('Invalid input');
77
78
expect(() => {
79
throw new TypeError('Expected string');
80
}).toThrowError(TypeError);
81
```
82
83
## Advanced Usage Patterns
84
85
### Testing Custom Error Classes
86
87
```javascript
88
class ValidationError extends Error {
89
constructor(field, message) {
90
super(`Validation failed for ${field}: ${message}`);
91
this.name = 'ValidationError';
92
this.field = field;
93
}
94
}
95
96
class NetworkError extends Error {
97
constructor(statusCode, message) {
98
super(message);
99
this.name = 'NetworkError';
100
this.statusCode = statusCode;
101
}
102
}
103
104
function validateUser(userData) {
105
if (!userData.email) {
106
throw new ValidationError('email', 'Email is required');
107
}
108
if (!userData.email.includes('@')) {
109
throw new ValidationError('email', 'Invalid email format');
110
}
111
}
112
113
// Test specific error types
114
expect(() => validateUser({})).toThrow(ValidationError);
115
116
// Test error messages
117
expect(() => validateUser({})).toThrow('Validation failed for email: Email is required');
118
119
// Test with regex for flexible matching
120
expect(() => validateUser({email: 'invalid'})).toThrow(/Validation failed for email:/);
121
122
// Test that valid data doesn't throw
123
expect(() => validateUser({email: 'user@example.com'})).not.toThrow();
124
```
125
126
### Testing Async Functions
127
128
```javascript
129
// Testing async functions that throw
130
async function fetchUserData(userId) {
131
if (!userId) {
132
throw new Error('User ID is required');
133
}
134
if (userId < 0) {
135
throw new RangeError('User ID must be positive');
136
}
137
// fetch implementation...
138
}
139
140
// Use await expect for async functions
141
await expect(async () => {
142
await fetchUserData();
143
}).rejects.toThrow('User ID is required');
144
145
await expect(async () => {
146
await fetchUserData(-1);
147
}).rejects.toThrow(RangeError);
148
149
// Alternative syntax
150
expect(fetchUserData()).rejects.toThrow('User ID is required');
151
expect(fetchUserData(-1)).rejects.toThrow(RangeError);
152
```
153
154
### Testing Promise Rejections
155
156
```javascript
157
function processData(data) {
158
return new Promise((resolve, reject) => {
159
if (!data) {
160
reject(new Error('No data provided'));
161
} else if (data.invalid) {
162
reject(new ValidationError('data', 'Invalid data structure'));
163
} else {
164
resolve(data);
165
}
166
});
167
}
168
169
// Test promise rejections
170
await expect(processData()).rejects.toThrow('No data provided');
171
await expect(processData({invalid: true})).rejects.toThrow(ValidationError);
172
173
// Test successful resolution
174
await expect(processData({valid: true})).resolves.toEqual({valid: true});
175
```
176
177
### Testing Error Handling in Complex Scenarios
178
179
```javascript
180
class ApiClient {
181
async request(endpoint, options = {}) {
182
if (!endpoint) {
183
throw new Error('Endpoint is required');
184
}
185
186
if (options.timeout && options.timeout < 0) {
187
throw new RangeError('Timeout must be positive');
188
}
189
190
// Simulate network error
191
if (endpoint.includes('unavailable')) {
192
throw new NetworkError(503, 'Service unavailable');
193
}
194
195
return {data: 'success'};
196
}
197
}
198
199
const client = new ApiClient();
200
201
// Test various error conditions
202
await expect(() => client.request()).rejects.toThrow('Endpoint is required');
203
204
await expect(() =>
205
client.request('/api/data', {timeout: -1})
206
).rejects.toThrow(RangeError);
207
208
await expect(() =>
209
client.request('/api/unavailable')
210
).rejects.toThrow(NetworkError);
211
212
await expect(() =>
213
client.request('/api/unavailable')
214
).rejects.toThrow('Service unavailable');
215
216
// Test successful case
217
await expect(client.request('/api/data')).resolves.toEqual({data: 'success'});
218
```
219
220
### Testing Error Boundaries and Try-Catch Blocks
221
222
```javascript
223
function safeParseJSON(jsonString) {
224
try {
225
return JSON.parse(jsonString);
226
} catch (error) {
227
throw new Error(`Invalid JSON: ${error.message}`);
228
}
229
}
230
231
function unsafeParseJSON(jsonString) {
232
return JSON.parse(jsonString); // Let SyntaxError bubble up
233
}
234
235
// Test wrapped error handling
236
expect(() => safeParseJSON('{')).toThrow('Invalid JSON:');
237
expect(() => safeParseJSON('{')).toThrow(/Invalid JSON:/);
238
239
// Test raw error bubbling
240
expect(() => unsafeParseJSON('{')).toThrow(SyntaxError);
241
expect(() => unsafeParseJSON('{')).toThrow('Unexpected end of JSON input');
242
243
// Test successful parsing
244
expect(() => safeParseJSON('{"valid": true}')).not.toThrow();
245
expect(safeParseJSON('{"valid": true}')).toEqual({valid: true});
246
```
247
248
### Testing Multiple Error Conditions
249
250
```javascript
251
function divide(a, b) {
252
if (typeof a !== 'number' || typeof b !== 'number') {
253
throw new TypeError('Both arguments must be numbers');
254
}
255
if (b === 0) {
256
throw new Error('Division by zero');
257
}
258
if (!Number.isFinite(a) || !Number.isFinite(b)) {
259
throw new RangeError('Arguments must be finite numbers');
260
}
261
return a / b;
262
}
263
264
// Test type errors
265
expect(() => divide('5', 2)).toThrow(TypeError);
266
expect(() => divide(5, '2')).toThrow('Both arguments must be numbers');
267
268
// Test division by zero
269
expect(() => divide(10, 0)).toThrow('Division by zero');
270
271
// Test infinite numbers
272
expect(() => divide(Infinity, 5)).toThrow(RangeError);
273
expect(() => divide(5, NaN)).toThrow('Arguments must be finite numbers');
274
275
// Test successful operation
276
expect(() => divide(10, 2)).not.toThrow();
277
expect(divide(10, 2)).toBe(5);
278
```
279
280
## Testing Error Messages with Internationalization
281
282
```javascript
283
const ERROR_MESSAGES = {
284
en: {
285
REQUIRED_FIELD: 'This field is required',
286
INVALID_EMAIL: 'Please enter a valid email address'
287
},
288
es: {
289
REQUIRED_FIELD: 'Este campo es obligatorio',
290
INVALID_EMAIL: 'Por favor ingrese una dirección de correo válida'
291
}
292
};
293
294
function validateWithLocale(value, locale = 'en') {
295
if (!value) {
296
throw new Error(ERROR_MESSAGES[locale].REQUIRED_FIELD);
297
}
298
}
299
300
// Test different locales
301
expect(() => validateWithLocale('', 'en')).toThrow('This field is required');
302
expect(() => validateWithLocale('', 'es')).toThrow('Este campo es obligatorio');
303
304
// Test with regex for flexible matching
305
expect(() => validateWithLocale('', 'en')).toThrow(/required/i);
306
expect(() => validateWithLocale('', 'es')).toThrow(/obligatorio/i);
307
```
308
309
## Negation Support
310
311
Exception matchers support negation to test that functions do NOT throw:
312
313
```javascript
314
function safeOperation(value) {
315
if (value > 0) {
316
return value * 2;
317
}
318
return 0;
319
}
320
321
expect(() => safeOperation(5)).not.toThrow();
322
expect(() => safeOperation(0)).not.toThrow();
323
expect(() => safeOperation(-1)).not.toThrow();
324
325
// Test that async functions don't reject
326
await expect(Promise.resolve('success')).resolves.not.toThrow();
327
```
328
329
## Promise Support
330
331
Exception matchers work seamlessly with promise-based testing:
332
333
```javascript
334
// Test promise rejections
335
await expect(Promise.reject(new Error('Failed'))).rejects.toThrow('Failed');
336
await expect(Promise.reject(new TypeError('Type error'))).rejects.toThrow(TypeError);
337
338
// Test promise resolutions (should not throw)
339
await expect(Promise.resolve('success')).resolves.not.toThrow();
340
```