0
# Error and Exception Assertions
1
2
Methods for testing function exceptions, error throwing, and error property validation.
3
4
## Exception Testing
5
6
### throw()
7
8
Test that a function throws an exception.
9
10
```javascript { .api }
11
/**
12
* Assert that the function throws an exception
13
* @param message - Optional expected error message (string, RegExp, or Function)
14
* @param properties - Optional expected error properties
15
* @returns This assertion for chaining
16
*/
17
throw(): Assertion;
18
throw(message: RegExp | string | Function, properties?: object): Assertion;
19
throw(properties: object): Assertion;
20
```
21
22
**Usage:**
23
```javascript
24
import should from 'should';
25
26
// Basic exception testing
27
(() => {
28
throw new Error('Something went wrong');
29
}).should.throw();
30
31
// Test specific error message
32
(() => {
33
throw new Error('Invalid input');
34
}).should.throw('Invalid input');
35
36
// Test error message with regex
37
(() => {
38
throw new Error('User not found: ID 123');
39
}).should.throw(/User not found/);
40
41
// Test error type
42
(() => {
43
throw new TypeError('Expected string');
44
}).should.throw(TypeError);
45
46
// Test error properties
47
(() => {
48
const error = new Error('Validation failed');
49
error.code = 400;
50
error.field = 'email';
51
throw error;
52
}).should.throw({ code: 400, field: 'email' });
53
54
// Combined message and properties
55
(() => {
56
const error = new Error('Database connection failed');
57
error.errno = -61;
58
error.code = 'ECONNREFUSED';
59
throw error;
60
}).should.throw('Database connection failed', { code: 'ECONNREFUSED' });
61
```
62
63
### throwError()
64
65
Alias for `throw()` with identical functionality.
66
67
```javascript { .api }
68
/**
69
* Assert that the function throws an error - alias for throw()
70
* @param message - Optional expected error message (string, RegExp, or Function)
71
* @param properties - Optional expected error properties
72
* @returns This assertion for chaining
73
*/
74
throwError(): Assertion;
75
throwError(message: RegExp | string | Function, properties?: object): Assertion;
76
throwError(properties: object): Assertion;
77
```
78
79
**Usage:**
80
```javascript
81
// Same functionality as throw()
82
(() => {
83
throw new Error('Critical failure');
84
}).should.throwError();
85
86
(() => {
87
throw new Error('Access denied');
88
}).should.throwError('Access denied');
89
90
(() => {
91
throw new RangeError('Index out of bounds');
92
}).should.throwError(RangeError);
93
```
94
95
## Specific Error Types
96
97
### Testing Different Error Types
98
```javascript
99
// TypeError testing
100
(() => {
101
const obj = null;
102
obj.property; // Will throw TypeError
103
}).should.throw(TypeError);
104
105
// RangeError testing
106
(() => {
107
const arr = new Array(-1); // Invalid array length
108
}).should.throw(RangeError);
109
110
// SyntaxError testing (in eval)
111
(() => {
112
eval('invalid javascript syntax !!!');
113
}).should.throw(SyntaxError);
114
115
// Custom error types
116
class ValidationError extends Error {
117
constructor(message, field) {
118
super(message);
119
this.name = 'ValidationError';
120
this.field = field;
121
}
122
}
123
124
(() => {
125
throw new ValidationError('Invalid email format', 'email');
126
}).should.throw(ValidationError);
127
```
128
129
## Async Function Testing
130
131
### Testing Async Functions That Throw
132
```javascript
133
// Promise-based async functions
134
async function asyncFunction() {
135
throw new Error('Async error');
136
}
137
138
// Test async function (returns rejected promise)
139
asyncFunction().should.be.rejected();
140
141
// With specific error
142
async function validateUser(id) {
143
if (!id) {
144
throw new Error('User ID is required');
145
}
146
// ... more logic
147
}
148
149
validateUser().should.be.rejectedWith('User ID is required');
150
```
151
152
### Testing Callback-based Functions
153
```javascript
154
function processData(data, callback) {
155
if (!data) {
156
callback(new Error('Data is required'));
157
return;
158
}
159
// Process data...
160
callback(null, processedData);
161
}
162
163
// Test callback error
164
function testCallback() {
165
processData(null, (err, result) => {
166
should.exist(err);
167
err.should.be.an.Error();
168
err.message.should.equal('Data is required');
169
should.not.exist(result);
170
});
171
}
172
```
173
174
## Error Property Validation
175
176
### Testing Custom Error Properties
177
```javascript
178
class APIError extends Error {
179
constructor(message, statusCode, details) {
180
super(message);
181
this.name = 'APIError';
182
this.statusCode = statusCode;
183
this.details = details;
184
}
185
}
186
187
function callAPI() {
188
throw new APIError('Request failed', 404, {
189
endpoint: '/users/123',
190
method: 'GET',
191
timestamp: Date.now()
192
});
193
}
194
195
// Test comprehensive error properties
196
(() => callAPI()).should.throw(APIError)
197
.and.have.property('statusCode', 404)
198
.and.have.property('details')
199
.which.has.property('endpoint');
200
201
// Alternative syntax
202
try {
203
callAPI();
204
should.fail('Expected APIError to be thrown');
205
} catch (error) {
206
error.should.be.instanceof(APIError);
207
error.should.have.property('message', 'Request failed');
208
error.should.have.property('statusCode', 404);
209
error.details.should.have.properties('endpoint', 'method');
210
error.details.endpoint.should.equal('/users/123');
211
}
212
```
213
214
## Validation Function Testing
215
216
### Input Validation
217
```javascript
218
function validateEmail(email) {
219
if (typeof email !== 'string') {
220
throw new TypeError('Email must be a string');
221
}
222
if (!email.includes('@')) {
223
throw new Error('Invalid email format');
224
}
225
if (email.length < 5) {
226
throw new Error('Email too short');
227
}
228
}
229
230
// Test various validation scenarios
231
(() => validateEmail(null)).should.throw(TypeError, 'Email must be a string');
232
(() => validateEmail('invalid')).should.throw('Invalid email format');
233
(() => validateEmail('a@b')).should.throw('Email too short');
234
(() => validateEmail('valid@email.com')).should.not.throw();
235
```
236
237
### Range Validation
238
```javascript
239
function validateAge(age) {
240
if (typeof age !== 'number') {
241
throw new TypeError('Age must be a number');
242
}
243
if (age < 0) {
244
throw new RangeError('Age cannot be negative');
245
}
246
if (age > 150) {
247
throw new RangeError('Age cannot exceed 150');
248
}
249
}
250
251
// Test age validation
252
(() => validateAge('25')).should.throw(TypeError);
253
(() => validateAge(-5)).should.throw(RangeError, 'Age cannot be negative');
254
(() => validateAge(200)).should.throw(RangeError, 'Age cannot exceed 150');
255
(() => validateAge(25)).should.not.throw();
256
```
257
258
### Configuration Validation
259
```javascript
260
function validateConfig(config) {
261
if (!config) {
262
throw new Error('Configuration is required');
263
}
264
if (!config.apiKey) {
265
const error = new Error('API key is missing');
266
error.code = 'CONFIG_MISSING_API_KEY';
267
throw error;
268
}
269
if (typeof config.timeout !== 'number' || config.timeout <= 0) {
270
const error = new Error('Timeout must be a positive number');
271
error.code = 'CONFIG_INVALID_TIMEOUT';
272
error.received = config.timeout;
273
throw error;
274
}
275
}
276
277
// Test configuration validation
278
(() => validateConfig(null)).should.throw('Configuration is required');
279
280
(() => validateConfig({})).should.throw({ code: 'CONFIG_MISSING_API_KEY' });
281
282
(() => validateConfig({
283
apiKey: 'test',
284
timeout: -1
285
})).should.throw('Timeout must be a positive number', {
286
code: 'CONFIG_INVALID_TIMEOUT',
287
received: -1
288
});
289
```
290
291
## Database and Network Error Testing
292
293
### Database Error Simulation
294
```javascript
295
class DatabaseError extends Error {
296
constructor(message, query, errno) {
297
super(message);
298
this.name = 'DatabaseError';
299
this.query = query;
300
this.errno = errno;
301
}
302
}
303
304
function executeQuery(sql) {
305
if (!sql) {
306
throw new DatabaseError('SQL query is required', null, 1001);
307
}
308
if (sql.includes('DROP TABLE')) {
309
throw new DatabaseError('DROP TABLE not allowed', sql, 1142);
310
}
311
// ... execute query
312
}
313
314
// Test database error handling
315
(() => executeQuery('')).should.throw(DatabaseError)
316
.with.property('errno', 1001);
317
318
(() => executeQuery('DROP TABLE users')).should.throw(DatabaseError)
319
.with.property('errno', 1142)
320
.and.have.property('query', 'DROP TABLE users');
321
```
322
323
### Network Error Simulation
324
```javascript
325
class NetworkError extends Error {
326
constructor(message, statusCode, url) {
327
super(message);
328
this.name = 'NetworkError';
329
this.statusCode = statusCode;
330
this.url = url;
331
}
332
}
333
334
function fetchData(url) {
335
if (!url.startsWith('https://')) {
336
throw new NetworkError('Only HTTPS URLs allowed', 400, url);
337
}
338
if (url.includes('blocked.com')) {
339
throw new NetworkError('Domain is blocked', 403, url);
340
}
341
// ... fetch data
342
}
343
344
// Test network error scenarios
345
(() => fetchData('http://insecure.com')).should.throw(NetworkError)
346
.with.properties({
347
statusCode: 400,
348
url: 'http://insecure.com'
349
});
350
351
(() => fetchData('https://blocked.com/api')).should.throw(NetworkError)
352
.with.property('statusCode', 403);
353
```
354
355
## Testing Functions That Should NOT Throw
356
357
### Successful Execution Testing
358
```javascript
359
function safeOperation(data) {
360
// This function should not throw for valid input
361
return data.toString().toUpperCase();
362
}
363
364
function robustParser(input) {
365
try {
366
return JSON.parse(input);
367
} catch (e) {
368
return null; // Return null instead of throwing
369
}
370
}
371
372
// Test that functions don't throw
373
(() => safeOperation('hello')).should.not.throw();
374
(() => safeOperation(123)).should.not.throw();
375
(() => robustParser('invalid json')).should.not.throw();
376
377
// Test return values of non-throwing functions
378
safeOperation('hello').should.equal('HELLO');
379
should(robustParser('invalid json')).be.null();
380
should(robustParser('{"valid": true}')).eql({ valid: true });
381
```
382
383
## Complex Error Scenario Testing
384
385
### Multiple Error Conditions
386
```javascript
387
function complexValidation(user) {
388
const errors = [];
389
390
if (!user.name || user.name.length < 2) {
391
errors.push({ field: 'name', message: 'Name must be at least 2 characters' });
392
}
393
394
if (!user.email || !user.email.includes('@')) {
395
errors.push({ field: 'email', message: 'Valid email is required' });
396
}
397
398
if (user.age !== undefined && (user.age < 0 || user.age > 150)) {
399
errors.push({ field: 'age', message: 'Age must be between 0 and 150' });
400
}
401
402
if (errors.length > 0) {
403
const error = new Error('Validation failed');
404
error.name = 'ValidationError';
405
error.errors = errors;
406
throw error;
407
}
408
409
return user;
410
}
411
412
// Test multiple validation errors
413
(() => complexValidation({
414
name: 'A',
415
email: 'invalid',
416
age: -5
417
})).should.throw('Validation failed')
418
.with.property('errors')
419
.which.has.length(3);
420
421
// Test successful validation
422
(() => complexValidation({
423
name: 'John Doe',
424
email: 'john@example.com',
425
age: 30
426
})).should.not.throw();
427
```
428
429
## Chaining and Negation
430
431
```javascript
432
const throwingFunction = () => {
433
throw new Error('Test error');
434
};
435
436
const nonThrowingFunction = () => {
437
return 'success';
438
};
439
440
// Chaining with other assertions
441
throwingFunction.should.be.a.Function().and.throw();
442
nonThrowingFunction.should.be.a.Function().and.not.throw();
443
444
// Complex chaining
445
(() => {
446
const error = new Error('Custom error');
447
error.code = 500;
448
throw error;
449
}).should.throw()
450
.with.property('message', 'Custom error')
451
.and.have.property('code', 500);
452
```