Skip to content

Commit 7a94ce8

Browse files
committed
assert: add rejects() and doesNotReject()
Implement asynchronous equivalent for assert.throws() and assert.doesNotThrow().
1 parent a910320 commit 7a94ce8

File tree

4 files changed

+210
-15
lines changed

4 files changed

+210
-15
lines changed

doc/api/assert.md

+84
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,45 @@ 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+
> Stability: 1 - Experimental
324+
325+
Awaits for the promise returned by function `block` to complete and not be
326+
rejected. See [`assert.rejects()`][] for more details.
327+
328+
When `assert.doesNotReject()` is called, it will immediately call the `block`
329+
function, and awaits for completion.
330+
331+
Besides the async nature to await the completion it behaves identical to
332+
[`assert.doesNotThrow()`][].
333+
334+
```js
335+
(async () => {
336+
await assert.doesNotReject(
337+
async () => {
338+
throw new TypeError('Wrong value');
339+
},
340+
SyntaxError
341+
);
342+
})();
343+
```
344+
345+
```js
346+
assert.doesNotReject(
347+
() => Promise.reject(new TypeError('Wrong value')),
348+
SyntaxError
349+
).then(() => {
350+
// ...
351+
});
352+
```
353+
315354
## assert.doesNotThrow(block[, error][, message])
316355
<!-- YAML
317356
added: v0.1.21
@@ -836,6 +875,50 @@ If the values are not strictly equal, an `AssertionError` is thrown with a
836875
`message` parameter is an instance of an [`Error`][] then it will be thrown
837876
instead of the `AssertionError`.
838877

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