Skip to content

Commit 3babc5b

Browse files
feugyMylesBorins
authored andcommitted
assert: add rejects() and doesNotReject()
Implement asynchronous equivalent for assert.throws() and assert.doesNotThrow(). Backport-PR-URL: #24019 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 7f34c27 commit 3babc5b

File tree

4 files changed

+207
-15
lines changed

4 files changed

+207
-15
lines changed

doc/api/assert.md

+80
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,43 @@ If the values are not equal, an `AssertionError` is thrown with a `message`
242242
property set equal to the value of the `message` parameter. If the `message`
243243
parameter is undefined, a default error message is assigned.
244244

245+
## assert.doesNotReject(block[, error][, message])
246+
<!-- YAML
247+
added: REPLACEME
248+
-->
249+
* `block` {Function}
250+
* `error` {RegExp|Function}
251+
* `message` {any}
252+
253+
Awaits for the promise returned by function `block` to complete and not be
254+
rejected. See [`assert.rejects()`][] for more details.
255+
256+
When `assert.doesNotReject()` is called, it will immediately call the `block`
257+
function, and awaits for completion.
258+
259+
Besides the async nature to await the completion behaves identical to
260+
[`assert.doesNotThrow()`][].
261+
262+
```js
263+
(async () => {
264+
await assert.doesNotReject(
265+
async () => {
266+
throw new TypeError('Wrong value');
267+
},
268+
SyntaxError
269+
);
270+
})();
271+
```
272+
273+
```js
274+
assert.doesNotReject(
275+
() => Promise.reject(new TypeError('Wrong value')),
276+
SyntaxError
277+
).then(() => {
278+
// ...
279+
});
280+
```
281+
245282
## assert.doesNotThrow(block[, error][, message])
246283
<!-- YAML
247284
added: v0.1.21
@@ -631,6 +668,48 @@ If the values are not strictly equal, an `AssertionError` is thrown with a
631668
`message` property set equal to the value of the `message` parameter. If the
632669
`message` parameter is undefined, a default error message is assigned.
633670

671+
## assert.rejects(block[, error][, message])
672+
<!-- YAML
673+
added: REPLACEME
674+
-->
675+
* `block` {Function}
676+
* `error` {RegExp|Function|Object}
677+
* `message` {any}
678+
679+
Awaits for promise returned by function `block` to be rejected.
680+
681+
When `assert.rejects()` is called, it will immediately call the `block`
682+
function, and awaits for completion.
683+
684+
Besides the async nature to await the completion behaves identical to
685+
[`assert.throws()`][].
686+
687+
If specified, `error` can be a constructor, [`RegExp`][], a validation
688+
function, or an object where each property will be tested for.
689+
690+
If specified, `message` will be the message provided by the `AssertionError` if
691+
the block fails to reject.
692+
693+
```js
694+
(async () => {
695+
await assert.rejects(
696+
async () => {
697+
throw new Error('Wrong value');
698+
},
699+
Error
700+
);
701+
})();
702+
```
703+
704+
```js
705+
assert.rejects(
706+
() => Promise.reject(new Error('Wrong value')),
707+
Error
708+
).then(() => {
709+
// ...
710+
});
711+
```
712+
634713
## assert.throws(block[, error][, message])
635714
<!-- YAML
636715
added: v0.1.21
@@ -786,6 +865,7 @@ For more information, see
786865
[`assert.ok()`]: #assert_assert_ok_value_message
787866
[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
788867
[`assert.throws()`]: #assert_assert_throws_block_error_message
868+
[`assert.rejects()`]: #assert_assert_rejects_block_error_message
789869
[`strict mode`]: #assert_strict_mode
790870
[Abstract Equality Comparison]: https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
791871
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring

lib/assert.js

+50-15
Original file line numberDiff line numberDiff line change
@@ -718,17 +718,27 @@ function getActual(block) {
718718
}
719719
}
720720

721-
// Expected to throw an error.
722-
assert.throws = function throws(block, error, message) {
723-
const actual = getActual(block);
721+
async function waitForActual(block) {
722+
if (typeof block !== 'function') {
723+
throw new errors.ERR_INVALID_ARG_TYPE('block', 'Function', block);
724+
}
725+
try {
726+
await block();
727+
} catch (e) {
728+
return e;
729+
}
730+
return errors.NO_EXCEPTION_SENTINEL;
731+
}
724732

733+
// Expected to throw an error.
734+
function expectsError(stackStartFn, actual, error, message) {
725735
if (typeof error === 'string') {
726-
if (arguments.length === 3)
736+
if (arguments.length === 4) {
727737
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
728738
'error',
729739
['Function', 'RegExp'],
730740
error);
731-
741+
}
732742
message = error;
733743
error = null;
734744
}
@@ -739,21 +749,21 @@ assert.throws = function throws(block, error, message) {
739749
details += ` (${error.name})`;
740750
}
741751
details += message ? `: ${message}` : '.';
752+
const fnType = stackStartFn === rejects ? 'rejection' : 'exception';
742753
innerFail({
743754
actual,
744755
expected: error,
745-
operator: 'throws',
746-
message: `Missing expected exception${details}`,
747-
stackStartFn: throws
756+
operator: stackStartFn.name,
757+
message: `Missing expected ${fnType}${details}`,
758+
stackStartFn
748759
});
749760
}
750761
if (error && expectedException(actual, error, message) === false) {
751762
throw actual;
752763
}
753-
};
764+
}
754765

755-
assert.doesNotThrow = function doesNotThrow(block, error, message) {
756-
const actual = getActual(block);
766+
function expectsNoError(stackStartFn, actual, error, message) {
757767
if (actual === undefined)
758768
return;
759769

@@ -764,16 +774,41 @@ assert.doesNotThrow = function doesNotThrow(block, error, message) {
764774

765775
if (!error || expectedException(actual, error)) {
766776
const details = message ? `: ${message}` : '.';
777+
const fnType = stackStartFn === doesNotReject ? 'rejection' : 'exception';
767778
innerFail({
768779
actual,
769780
expected: error,
770-
operator: 'doesNotThrow',
771-
message: `Got unwanted exception${details}\n${actual.message}`,
772-
stackStartFn: doesNotThrow
781+
operator: stackStartFn.name,
782+
message: `Got unwanted ${fnType}${details}\n${actual.message}`,
783+
stackStartFn
773784
});
774785
}
775786
throw actual;
776-
};
787+
}
788+
789+
function throws(block, ...args) {
790+
expectsError(throws, getActual(block), ...args);
791+
}
792+
793+
assert.throws = throws;
794+
795+
async function rejects(block, ...args) {
796+
expectsError(rejects, await waitForActual(block), ...args);
797+
}
798+
799+
assert.rejects = rejects;
800+
801+
function doesNotThrow(block, ...args) {
802+
expectsNoError(doesNotThrow, getActual(block), ...args);
803+
}
804+
805+
assert.doesNotThrow = doesNotThrow;
806+
807+
async function doesNotReject(block, ...args) {
808+
expectsNoError(doesNotReject, await waitForActual(block), ...args);
809+
}
810+
811+
assert.doesNotReject = doesNotReject;
777812

778813
assert.ifError = function ifError(err) { if (err) throw err; };
779814

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
@@ -443,6 +443,7 @@ assert.throws(makeBlock(thrower, TypeError));
443443
} catch (e) {
444444
threw = true;
445445
assert.ok(e instanceof a.AssertionError);
446+
assert.ok(!e.stack.includes('at Function.doesNotThrow'));
446447
}
447448
assert.strictEqual(true, threw,
448449
'a.doesNotThrow is not catching type matching errors');
@@ -544,6 +545,16 @@ a.throws(makeBlock(thrower, TypeError), (err) => {
544545
code: 'ERR_ASSERTION',
545546
message: /^Missing expected exception \(TypeError\): fhqwhgads$/
546547
}));
548+
549+
let threw = false;
550+
try {
551+
a.throws(noop);
552+
} catch (e) {
553+
threw = true;
554+
assert.ok(e instanceof a.AssertionError);
555+
assert.ok(!e.stack.includes('at Function.throws'));
556+
}
557+
assert.ok(threw);
547558
}
548559

549560
const circular = { y: 1 };

0 commit comments

Comments
 (0)