Skip to content

Commit 599337f

Browse files
feugyBridgeAR
authored andcommitted
assert: add rejects() and doesNotReject()
Implement asynchronous equivalent for assert.throws() and assert.doesNotThrow(). PR-URL: #18023 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Shingo Inoue <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent 580ad01 commit 599337f

File tree

4 files changed

+206
-15
lines changed

4 files changed

+206
-15
lines changed

doc/api/assert.md

+80
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,43 @@ parameter is undefined, a default error message is assigned. If the `message`
312312
parameter is an instance of an [`Error`][] then it will be thrown instead of the
313313
`AssertionError`.
314314

315+
## assert.doesNotReject(block[, error][, message])
316+
<!-- YAML
317+
added: REPLACEME
318+
-->
319+
* `block` {Function}
320+
* `error` {RegExp|Function}
321+
* `message` {any}
322+
323+
Awaits for the promise returned by function `block` to complete and not be
324+
rejected. See [`assert.rejects()`][] for more details.
325+
326+
When `assert.doesNotReject()` is called, it will immediately call the `block`
327+
function, and awaits for completion.
328+
329+
Besides the async nature to await the completion behaves identical to
330+
[`assert.doesNotThrow()`][].
331+
332+
```js
333+
(async () => {
334+
await assert.doesNotReject(
335+
async () => {
336+
throw new TypeError('Wrong value');
337+
},
338+
SyntaxError
339+
);
340+
})();
341+
```
342+
343+
```js
344+
assert.doesNotReject(
345+
() => Promise.reject(new TypeError('Wrong value')),
346+
SyntaxError
347+
).then(() => {
348+
// ...
349+
});
350+
```
351+
315352
## assert.doesNotThrow(block[, error][, message])
316353
<!-- YAML
317354
added: v0.1.21
@@ -841,6 +878,48 @@ If the values are not strictly equal, an `AssertionError` is thrown with a
841878
`message` parameter is an instance of an [`Error`][] then it will be thrown
842879
instead of the `AssertionError`.
843880

881+
## assert.rejects(block[, error][, message])
882+
<!-- YAML
883+
added: REPLACEME
884+
-->
885+
* `block` {Function}
886+
* `error` {RegExp|Function|Object}
887+
* `message` {any}
888+
889+
Awaits for promise returned by function `block` to be rejected.
890+
891+
When `assert.rejects()` is called, it will immediately call the `block`
892+
function, and awaits for completion.
893+
894+
Besides the async nature to await the completion behaves identical to
895+
[`assert.throws()`][].
896+
897+
If specified, `error` can be a constructor, [`RegExp`][], a validation
898+
function, or an object where each property will be tested for.
899+
900+
If specified, `message` will be the message provided by the `AssertionError` if
901+
the block fails to reject.
902+
903+
```js
904+
(async () => {
905+
await assert.rejects(
906+
async () => {
907+
throw new Error('Wrong value');
908+
},
909+
Error
910+
);
911+
})();
912+
```
913+
914+
```js
915+
assert.rejects(
916+
() => Promise.reject(new Error('Wrong value')),
917+
Error
918+
).then(() => {
919+
// ...
920+
});
921+
```
922+
844923
## assert.throws(block[, error][, message])
845924
<!-- YAML
846925
added: v0.1.21
@@ -977,6 +1056,7 @@ second argument. This might lead to difficult-to-spot errors.
9771056
[`assert.ok()`]: #assert_assert_ok_value_message
9781057
[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
9791058
[`assert.throws()`]: #assert_assert_throws_block_error_message
1059+
[`assert.rejects()`]: #assert_assert_rejects_block_error_message
9801060
[`strict mode`]: #assert_strict_mode
9811061
[Abstract Equality Comparison]: https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
9821062
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring

lib/assert.js

+49-15
Original file line numberDiff line numberDiff line change
@@ -425,14 +425,23 @@ function getActual(block) {
425425
return NO_EXCEPTION_SENTINEL;
426426
}
427427

428-
// Expected to throw an error.
429-
assert.throws = function throws(block, error, message) {
430-
const actual = getActual(block);
428+
async function waitForActual(block) {
429+
if (typeof block !== 'function') {
430+
throw new ERR_INVALID_ARG_TYPE('block', 'Function', block);
431+
}
432+
try {
433+
await block();
434+
} catch (e) {
435+
return e;
436+
}
437+
return NO_EXCEPTION_SENTINEL;
438+
}
431439

440+
function expectsError(stackStartFn, actual, error, message) {
432441
if (typeof error === 'string') {
433-
if (arguments.length === 3)
442+
if (arguments.length === 4) {
434443
throw new ERR_INVALID_ARG_TYPE('error', ['Function', 'RegExp'], error);
435-
444+
}
436445
message = error;
437446
error = null;
438447
}
@@ -443,21 +452,21 @@ assert.throws = function throws(block, error, message) {
443452
details += ` (${error.name})`;
444453
}
445454
details += message ? `: ${message}` : '.';
455+
const fnType = stackStartFn === rejects ? 'rejection' : 'exception';
446456
innerFail({
447457
actual,
448458
expected: error,
449-
operator: 'throws',
450-
message: `Missing expected exception${details}`,
451-
stackStartFn: throws
459+
operator: stackStartFn.name,
460+
message: `Missing expected ${fnType}${details}`,
461+
stackStartFn
452462
});
453463
}
454464
if (error && expectedException(actual, error, message) === false) {
455465
throw actual;
456466
}
457-
};
467+
}
458468

459-
assert.doesNotThrow = function doesNotThrow(block, error, message) {
460-
const actual = getActual(block);
469+
function expectsNoError(stackStartFn, actual, error, message) {
461470
if (actual === NO_EXCEPTION_SENTINEL)
462471
return;
463472

@@ -468,16 +477,41 @@ assert.doesNotThrow = function doesNotThrow(block, error, message) {
468477

469478
if (!error || expectedException(actual, error)) {
470479
const details = message ? `: ${message}` : '.';
480+
const fnType = stackStartFn === doesNotReject ? 'rejection' : 'exception';
471481
innerFail({
472482
actual,
473483
expected: error,
474-
operator: 'doesNotThrow',
475-
message: `Got unwanted exception${details}\n${actual && actual.message}`,
476-
stackStartFn: doesNotThrow
484+
operator: stackStartFn.name,
485+
message: `Got unwanted ${fnType}${details}\n${actual && actual.message}`,
486+
stackStartFn
477487
});
478488
}
479489
throw actual;
480-
};
490+
}
491+
492+
function throws(block, ...args) {
493+
expectsError(throws, getActual(block), ...args);
494+
}
495+
496+
assert.throws = throws;
497+
498+
async function rejects(block, ...args) {
499+
expectsError(rejects, await waitForActual(block), ...args);
500+
}
501+
502+
assert.rejects = rejects;
503+
504+
function doesNotThrow(block, ...args) {
505+
expectsNoError(doesNotThrow, getActual(block), ...args);
506+
}
507+
508+
assert.doesNotThrow = doesNotThrow;
509+
510+
async function doesNotReject(block, ...args) {
511+
expectsNoError(doesNotReject, await waitForActual(block), ...args);
512+
}
513+
514+
assert.doesNotReject = doesNotReject;
481515

482516
assert.ifError = function ifError(err) {
483517
if (err !== null && err !== undefined) {

test/parallel/test-assert-async.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { promisify } = require('util');
5+
const wait = promisify(setTimeout);
6+
7+
/* eslint-disable prefer-common-expectserror, no-restricted-properties */
8+
9+
// Test assert.rejects() and assert.doesNotReject() by checking their
10+
// expected output and by verifying that they do not work sync
11+
12+
assert.rejects(
13+
() => assert.fail(),
14+
common.expectsError({
15+
code: 'ERR_ASSERTION',
16+
type: assert.AssertionError,
17+
message: 'Failed'
18+
})
19+
);
20+
21+
assert.doesNotReject(() => {});
22+
23+
{
24+
const promise = assert.rejects(async () => {
25+
await wait(1);
26+
assert.fail();
27+
}, common.expectsError({
28+
code: 'ERR_ASSERTION',
29+
type: assert.AssertionError,
30+
message: 'Failed'
31+
}));
32+
assert.doesNotReject(() => promise);
33+
}
34+
35+
{
36+
const promise = assert.doesNotReject(async () => {
37+
await wait(1);
38+
throw new Error();
39+
});
40+
assert.rejects(() => promise,
41+
(err) => {
42+
assert(err instanceof assert.AssertionError,
43+
`${err.name} is not instance of AssertionError`);
44+
assert.strictEqual(err.code, 'ERR_ASSERTION');
45+
assert(/^Got unwanted rejection\.\n$/.test(err.message));
46+
assert.strictEqual(err.operator, 'doesNotReject');
47+
assert.ok(!err.stack.includes('at Function.doesNotReject'));
48+
return true;
49+
}
50+
);
51+
}
52+
53+
{
54+
const promise = assert.rejects(() => {});
55+
assert.rejects(() => promise,
56+
(err) => {
57+
assert(err instanceof assert.AssertionError,
58+
`${err.name} is not instance of AssertionError`);
59+
assert.strictEqual(err.code, 'ERR_ASSERTION');
60+
assert(/^Missing expected rejection\.$/.test(err.message));
61+
assert.strictEqual(err.operator, 'rejects');
62+
assert.ok(!err.stack.includes('at Function.rejects'));
63+
return true;
64+
}
65+
);
66+
}

test/parallel/test-assert.js

+11
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ assert.throws(() => thrower(TypeError));
116116
} catch (e) {
117117
threw = true;
118118
assert.ok(e instanceof a.AssertionError);
119+
assert.ok(!e.stack.includes('at Function.doesNotThrow'));
119120
}
120121
assert.ok(threw, 'a.doesNotThrow is not catching type matching errors');
121122
}
@@ -221,6 +222,16 @@ a.throws(() => thrower(TypeError), (err) => {
221222
code: 'ERR_ASSERTION',
222223
message: /^Missing expected exception \(TypeError\): fhqwhgads$/
223224
}));
225+
226+
let threw = false;
227+
try {
228+
a.throws(noop);
229+
} catch (e) {
230+
threw = true;
231+
assert.ok(e instanceof a.AssertionError);
232+
assert.ok(!e.stack.includes('at Function.throws'));
233+
}
234+
assert.ok(threw);
224235
}
225236

226237
const circular = { y: 1 };

0 commit comments

Comments
 (0)