0
# Promise Assertions
1
2
Methods for testing async functions, promise states, and asynchronous assertion chaining.
3
4
## Promise State Testing
5
6
### Promise()
7
8
Test that a value is a Promise.
9
10
```javascript { .api }
11
/**
12
* Assert that the value is a Promise
13
* @returns This assertion for chaining
14
*/
15
Promise(): Assertion;
16
```
17
18
**Usage:**
19
```javascript
20
import should from 'should';
21
22
// Basic Promise detection
23
Promise.resolve('value').should.be.a.Promise();
24
Promise.reject('error').should.be.a.Promise();
25
26
async function asyncFunc() {
27
return 'result';
28
}
29
asyncFunc().should.be.a.Promise();
30
31
// Function that returns a Promise
32
function createPromise() {
33
return new Promise((resolve) => {
34
setTimeout(() => resolve('done'), 100);
35
});
36
}
37
createPromise().should.be.a.Promise();
38
39
// Not a Promise
40
'string'.should.not.be.a.Promise();
41
123.should.not.be.a.Promise();
42
{}.should.not.be.a.Promise();
43
```
44
45
## Promise Resolution Testing
46
47
### fulfilled() / resolved()
48
49
Test that a Promise resolves successfully.
50
51
```javascript { .api }
52
/**
53
* Assert that the Promise resolves (fulfills) successfully
54
* @returns Promise<any> for async testing
55
*/
56
fulfilled(): Promise<any>;
57
resolved(): Promise<any>;
58
```
59
60
**Usage:**
61
```javascript
62
// Basic resolution testing
63
Promise.resolve('success').should.be.fulfilled();
64
Promise.resolve(42).should.be.resolved();
65
66
// Async function testing
67
async function successfulOperation() {
68
await new Promise(resolve => setTimeout(resolve, 10));
69
return 'completed';
70
}
71
successfulOperation().should.be.fulfilled();
72
73
// Testing with await
74
await Promise.resolve('value').should.be.fulfilled();
75
76
// Chain with other assertions using .finally
77
Promise.resolve('hello')
78
.should.be.fulfilled()
79
.finally.equal('hello');
80
```
81
82
### rejected()
83
84
Test that a Promise rejects.
85
86
```javascript { .api }
87
/**
88
* Assert that the Promise rejects
89
* @returns Promise<any> for async testing
90
*/
91
rejected(): Promise<any>;
92
```
93
94
**Usage:**
95
```javascript
96
// Basic rejection testing
97
Promise.reject(new Error('failed')).should.be.rejected();
98
99
// Async function that throws
100
async function failingOperation() {
101
throw new Error('Operation failed');
102
}
103
failingOperation().should.be.rejected();
104
105
// Testing with await
106
await Promise.reject('error').should.be.rejected();
107
108
// Network request simulation
109
async function fetchData(url) {
110
if (!url) {
111
throw new Error('URL is required');
112
}
113
// ... fetch logic
114
}
115
fetchData().should.be.rejected();
116
```
117
118
## Promise Value Testing
119
120
### fulfilledWith() / resolvedWith()
121
122
Test that a Promise resolves with a specific value.
123
124
```javascript { .api }
125
/**
126
* Assert that the Promise resolves with the specified value
127
* @param expected - The expected resolved value
128
* @returns Promise<any> for async testing
129
*/
130
fulfilledWith(expected: any): Promise<any>;
131
resolvedWith(expected: any): Promise<any>;
132
```
133
134
**Usage:**
135
```javascript
136
// Test specific resolved values
137
Promise.resolve('success').should.be.fulfilledWith('success');
138
Promise.resolve(42).should.be.resolvedWith(42);
139
140
// Object resolution
141
const user = { id: 1, name: 'john' };
142
Promise.resolve(user).should.be.fulfilledWith(user);
143
144
// Async function result testing
145
async function getUserById(id) {
146
// Simulate API call
147
await new Promise(resolve => setTimeout(resolve, 10));
148
return { id, name: `user_${id}` };
149
}
150
getUserById(123).should.be.fulfilledWith({ id: 123, name: 'user_123' });
151
152
// Array results
153
async function getUsers() {
154
return [
155
{ id: 1, name: 'john' },
156
{ id: 2, name: 'jane' }
157
];
158
}
159
getUsers().should.be.fulfilledWith([
160
{ id: 1, name: 'john' },
161
{ id: 2, name: 'jane' }
162
]);
163
164
// Testing with await
165
const result = await Promise.resolve('test').should.be.fulfilledWith('test');
166
```
167
168
### rejectedWith()
169
170
Test that a Promise rejects with a specific error or message.
171
172
```javascript { .api }
173
/**
174
* Assert that the Promise rejects with the specified error/message
175
* @param error - Expected error (RegExp, string, Error, or Function)
176
* @param properties - Optional expected error properties
177
* @returns Promise<any> for async testing
178
*/
179
rejectedWith(error: RegExp | string | Error, properties?: object): Promise<any>;
180
rejectedWith(properties: object): Promise<any>;
181
```
182
183
**Usage:**
184
```javascript
185
// Test specific error messages
186
Promise.reject(new Error('Not found')).should.be.rejectedWith('Not found');
187
188
// Test with RegExp
189
Promise.reject(new Error('User ID 123 not found'))
190
.should.be.rejectedWith(/User ID \d+ not found/);
191
192
// Test specific error types
193
Promise.reject(new TypeError('Invalid input'))
194
.should.be.rejectedWith(TypeError);
195
196
// Test error properties
197
const customError = new Error('Database error');
198
customError.code = 500;
199
customError.table = 'users';
200
Promise.reject(customError).should.be.rejectedWith('Database error', {
201
code: 500,
202
table: 'users'
203
});
204
205
// Async function error testing
206
async function validateUser(user) {
207
if (!user.email) {
208
const error = new Error('Email is required');
209
error.field = 'email';
210
throw error;
211
}
212
}
213
validateUser({}).should.be.rejectedWith('Email is required', { field: 'email' });
214
215
// API error simulation
216
async function apiCall(endpoint) {
217
if (endpoint === '/forbidden') {
218
const error = new Error('Access denied');
219
error.statusCode = 403;
220
throw error;
221
}
222
}
223
apiCall('/forbidden').should.be.rejectedWith({ statusCode: 403 });
224
```
225
226
## Async Assertion Chaining
227
228
### finally
229
230
Chain assertions after promise resolution.
231
232
```javascript { .api }
233
/**
234
* Chain assertions after promise settles (resolved or rejected)
235
*/
236
finally: PromisedAssertion;
237
```
238
239
**Usage:**
240
```javascript
241
// Chain assertions on resolved value
242
Promise.resolve('hello world')
243
.should.be.fulfilled()
244
.finally.be.a.String()
245
.and.have.length(11)
246
.and.startWith('hello');
247
248
// Chain with object properties
249
Promise.resolve({ id: 123, name: 'john', active: true })
250
.should.be.fulfilled()
251
.finally.have.property('id', 123)
252
.and.have.property('name')
253
.which.is.a.String()
254
.and.have.property('active', true);
255
256
// Chain with array assertions
257
Promise.resolve([1, 2, 3, 4, 5])
258
.should.be.fulfilled()
259
.finally.be.an.Array()
260
.and.have.length(5)
261
.and.containEql(3);
262
263
// Complex chaining
264
async function fetchUserProfile(userId) {
265
return {
266
id: userId,
267
profile: {
268
name: 'John Doe',
269
email: 'john@example.com',
270
preferences: { theme: 'dark' }
271
},
272
roles: ['user', 'member']
273
};
274
}
275
276
fetchUserProfile(123)
277
.should.be.fulfilled()
278
.finally.have.property('profile')
279
.which.has.property('preferences')
280
.which.has.property('theme', 'dark');
281
```
282
283
### eventually
284
285
Alias for `finally` - chain assertions after promise settlement.
286
287
```javascript { .api }
288
/**
289
* Chain assertions after promise settles - alias for finally
290
*/
291
eventually: PromisedAssertion;
292
```
293
294
**Usage:**
295
```javascript
296
// Same functionality as finally
297
Promise.resolve(42)
298
.should.be.fulfilled()
299
.eventually.be.a.Number()
300
.and.be.above(40);
301
302
// Object property testing
303
Promise.resolve({ count: 5, items: ['a', 'b', 'c', 'd', 'e'] })
304
.should.be.fulfilled()
305
.eventually.have.property('count', 5)
306
.and.have.property('items')
307
.which.has.length(5);
308
```
309
310
## Practical Async Testing Examples
311
312
### API Testing
313
```javascript
314
class APIClient {
315
async get(endpoint) {
316
if (endpoint === '/users/404') {
317
const error = new Error('User not found');
318
error.statusCode = 404;
319
throw error;
320
}
321
322
if (endpoint === '/users/1') {
323
return {
324
id: 1,
325
name: 'John Doe',
326
email: 'john@example.com',
327
createdAt: '2023-01-01T00:00:00Z'
328
};
329
}
330
331
return [];
332
}
333
334
async post(endpoint, data) {
335
if (!data.name) {
336
const error = new Error('Name is required');
337
error.field = 'name';
338
error.statusCode = 400;
339
throw error;
340
}
341
342
return {
343
id: Date.now(),
344
...data,
345
createdAt: new Date().toISOString()
346
};
347
}
348
}
349
350
const client = new APIClient();
351
352
// Test successful API calls
353
client.get('/users/1')
354
.should.be.fulfilled()
355
.finally.have.properties('id', 'name', 'email')
356
.and.have.property('id', 1);
357
358
// Test API errors
359
client.get('/users/404')
360
.should.be.rejectedWith('User not found', { statusCode: 404 });
361
362
client.post('/users', {})
363
.should.be.rejectedWith('Name is required', {
364
field: 'name',
365
statusCode: 400
366
});
367
368
// Test successful creation
369
client.post('/users', { name: 'Jane Doe', email: 'jane@example.com' })
370
.should.be.fulfilled()
371
.finally.have.property('name', 'Jane Doe')
372
.and.have.property('id')
373
.which.is.a.Number();
374
```
375
376
### Database Operations
377
```javascript
378
class UserRepository {
379
async findById(id) {
380
if (typeof id !== 'number') {
381
throw new TypeError('User ID must be a number');
382
}
383
384
if (id <= 0) {
385
throw new RangeError('User ID must be positive');
386
}
387
388
if (id === 999) {
389
return null; // User not found
390
}
391
392
return {
393
id,
394
name: `User ${id}`,
395
active: true
396
};
397
}
398
399
async create(userData) {
400
if (!userData.name) {
401
const error = new Error('User name is required');
402
error.code = 'VALIDATION_ERROR';
403
throw error;
404
}
405
406
return {
407
id: Math.floor(Math.random() * 1000),
408
...userData,
409
createdAt: new Date(),
410
active: true
411
};
412
}
413
}
414
415
const userRepo = new UserRepository();
416
417
// Test successful queries
418
userRepo.findById(1)
419
.should.be.fulfilled()
420
.finally.have.properties('id', 'name', 'active')
421
.and.have.property('active', true);
422
423
// Test not found scenario
424
userRepo.findById(999)
425
.should.be.fulfilledWith(null);
426
427
// Test validation errors
428
userRepo.findById('invalid')
429
.should.be.rejectedWith(TypeError);
430
431
userRepo.findById(-1)
432
.should.be.rejectedWith(RangeError);
433
434
userRepo.create({})
435
.should.be.rejectedWith('User name is required', { code: 'VALIDATION_ERROR' });
436
437
// Test successful creation
438
userRepo.create({ name: 'John Doe', email: 'john@example.com' })
439
.should.be.fulfilled()
440
.finally.have.property('name', 'John Doe')
441
.and.have.property('id')
442
.which.is.a.Number()
443
.and.be.above(0);
444
```
445
446
### File Operations
447
```javascript
448
const fs = require('fs').promises;
449
const path = require('path');
450
451
class FileManager {
452
async readConfig(filename) {
453
try {
454
const content = await fs.readFile(filename, 'utf8');
455
return JSON.parse(content);
456
} catch (error) {
457
if (error.code === 'ENOENT') {
458
const notFoundError = new Error(`Config file not found: ${filename}`);
459
notFoundError.code = 'CONFIG_NOT_FOUND';
460
throw notFoundError;
461
}
462
throw error;
463
}
464
}
465
466
async writeConfig(filename, config) {
467
if (!config || typeof config !== 'object') {
468
throw new TypeError('Config must be an object');
469
}
470
471
const content = JSON.stringify(config, null, 2);
472
await fs.writeFile(filename, content, 'utf8');
473
return { success: true, filename };
474
}
475
}
476
477
const fileManager = new FileManager();
478
479
// Test file reading
480
fileManager.readConfig('existing-config.json')
481
.should.be.fulfilled()
482
.finally.be.an.Object();
483
484
// Test missing file
485
fileManager.readConfig('missing-config.json')
486
.should.be.rejectedWith(/Config file not found/, { code: 'CONFIG_NOT_FOUND' });
487
488
// Test invalid config writing
489
fileManager.writeConfig('test.json', null)
490
.should.be.rejectedWith(TypeError, 'Config must be an object');
491
492
// Test successful writing
493
fileManager.writeConfig('test.json', { setting: 'value' })
494
.should.be.fulfilled()
495
.finally.have.properties('success', 'filename')
496
.and.have.property('success', true);
497
```
498
499
### Timeout and Retry Logic
500
```javascript
501
class RetryableOperation {
502
constructor(maxRetries = 3) {
503
this.maxRetries = maxRetries;
504
this.attempt = 0;
505
}
506
507
async execute() {
508
this.attempt++;
509
510
if (this.attempt <= 2) {
511
const error = new Error(`Attempt ${this.attempt} failed`);
512
error.attempt = this.attempt;
513
error.retryable = true;
514
throw error;
515
}
516
517
return {
518
success: true,
519
attempt: this.attempt,
520
message: 'Operation completed'
521
};
522
}
523
524
async executeWithTimeout(timeoutMs) {
525
return new Promise((resolve, reject) => {
526
const timer = setTimeout(() => {
527
const error = new Error('Operation timed out');
528
error.code = 'TIMEOUT';
529
reject(error);
530
}, timeoutMs);
531
532
this.execute()
533
.then(result => {
534
clearTimeout(timer);
535
resolve(result);
536
})
537
.catch(error => {
538
clearTimeout(timer);
539
reject(error);
540
});
541
});
542
}
543
}
544
545
// Test retry logic
546
const operation = new RetryableOperation();
547
548
// First attempts should fail
549
operation.execute()
550
.should.be.rejectedWith('Attempt 1 failed', {
551
attempt: 1,
552
retryable: true
553
});
554
555
// After retries, should succeed
556
const retryOperation = new RetryableOperation();
557
retryOperation.attempt = 2; // Skip to final attempt
558
retryOperation.execute()
559
.should.be.fulfilled()
560
.finally.have.properties('success', 'attempt', 'message')
561
.and.have.property('success', true);
562
563
// Test timeout
564
const timeoutOperation = new RetryableOperation();
565
timeoutOperation.executeWithTimeout(1) // Very short timeout
566
.should.be.rejectedWith('Operation timed out', { code: 'TIMEOUT' });
567
```
568
569
## Advanced Promise Testing
570
571
### Promise.all() Testing
572
```javascript
573
const promises = [
574
Promise.resolve(1),
575
Promise.resolve(2),
576
Promise.resolve(3)
577
];
578
579
Promise.all(promises)
580
.should.be.fulfilled()
581
.finally.eql([1, 2, 3]);
582
583
// Test Promise.all() with rejection
584
const mixedPromises = [
585
Promise.resolve('success'),
586
Promise.reject(new Error('failed')),
587
Promise.resolve('also success')
588
];
589
590
Promise.all(mixedPromises)
591
.should.be.rejectedWith('failed');
592
```
593
594
### Promise.race() Testing
595
```javascript
596
const racePromises = [
597
new Promise(resolve => setTimeout(() => resolve('slow'), 100)),
598
Promise.resolve('fast')
599
];
600
601
Promise.race(racePromises)
602
.should.be.fulfilledWith('fast');
603
```
604
605
## Negation and Error Cases
606
607
```javascript
608
// Negation examples
609
Promise.resolve('value').should.not.be.rejected();
610
Promise.reject('error').should.not.be.fulfilled();
611
612
'not a promise'.should.not.be.a.Promise();
613
Promise.resolve(42).should.not.be.fulfilledWith(43);
614
Promise.reject('wrong').should.not.be.rejectedWith('different error');
615
616
// Testing async functions that shouldn't throw
617
async function safeAsyncOperation() {
618
return 'safe result';
619
}
620
621
safeAsyncOperation().should.not.be.rejected();
622
safeAsyncOperation().should.be.fulfilledWith('safe result');
623
```