assert: add direct promises support in rejects
This adds direct promise support to `assert.rejects` and `assert.doesNotReject`. It will now accept both, functions and ES2015 promises as input. Besides this the documentation was updated to reflect the latest changes. It also refactors the tests to a non blocking way to improve the execution performance and improves the coverage. PR-URL: https://github.com/nodejs/node/pull/19885 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
This commit is contained in:
parent
11819c7773
commit
2c3146d06d
@ -378,22 +378,23 @@ parameter is an instance of an [`Error`][] then it will be thrown instead of the
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
-->
|
-->
|
||||||
* `block` {Function}
|
* `block` {Function|Promise}
|
||||||
* `error` {RegExp|Function}
|
* `error` {RegExp|Function}
|
||||||
* `message` {any}
|
* `message` {any}
|
||||||
|
|
||||||
Awaits for the promise returned by function `block` to complete and not be
|
Awaits the `block` promise or, if `block` is a function, immediately calls the
|
||||||
rejected.
|
function and awaits the returned promise to complete. It will then check that
|
||||||
|
the promise is not rejected.
|
||||||
|
|
||||||
|
If `block` is a function and it throws an error synchronously,
|
||||||
|
`assert.doesNotReject()` will return a rejected Promise with that error without
|
||||||
|
checking the error handler.
|
||||||
|
|
||||||
Please note: Using `assert.doesNotReject()` is actually not useful because there
|
Please note: Using `assert.doesNotReject()` is actually not useful because there
|
||||||
is little benefit by catching a rejection and then rejecting it again. Instead,
|
is little benefit by catching a rejection and then rejecting it again. Instead,
|
||||||
consider adding a comment next to the specific code path that should not reject
|
consider adding a comment next to the specific code path that should not reject
|
||||||
and keep error messages as expressive as possible.
|
and keep error messages as expressive as possible.
|
||||||
|
|
||||||
When `assert.doesNotReject()` is called, it will immediately call the `block`
|
|
||||||
function, and awaits for completion. See [`assert.rejects()`][] for more
|
|
||||||
details.
|
|
||||||
|
|
||||||
Besides the async nature to await the completion behaves identically to
|
Besides the async nature to await the completion behaves identically to
|
||||||
[`assert.doesNotThrow()`][].
|
[`assert.doesNotThrow()`][].
|
||||||
|
|
||||||
@ -409,12 +410,10 @@ Besides the async nature to await the completion behaves identically to
|
|||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
assert.doesNotReject(
|
assert.doesNotReject(Promise.reject(new TypeError('Wrong value')))
|
||||||
() => Promise.reject(new TypeError('Wrong value')),
|
.then(() => {
|
||||||
SyntaxError
|
// ...
|
||||||
).then(() => {
|
});
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## assert.doesNotThrow(block[, error][, message])
|
## assert.doesNotThrow(block[, error][, message])
|
||||||
@ -916,14 +915,17 @@ assert(0);
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
-->
|
-->
|
||||||
* `block` {Function}
|
* `block` {Function|Promise}
|
||||||
* `error` {RegExp|Function|Object}
|
* `error` {RegExp|Function|Object}
|
||||||
* `message` {any}
|
* `message` {any}
|
||||||
|
|
||||||
Awaits for promise returned by function `block` to be rejected.
|
Awaits the `block` promise or, if `block` is a function, immediately calls the
|
||||||
|
function and awaits the returned promise to complete. It will then check that
|
||||||
|
the promise is rejected.
|
||||||
|
|
||||||
When `assert.rejects()` is called, it will immediately call the `block`
|
If `block` is a function and it throws an error synchronously,
|
||||||
function, and awaits for completion.
|
`assert.rejects()` will return a rejected Promise with that error without
|
||||||
|
checking the error handler.
|
||||||
|
|
||||||
Besides the async nature to await the completion behaves identically to
|
Besides the async nature to await the completion behaves identically to
|
||||||
[`assert.throws()`][].
|
[`assert.throws()`][].
|
||||||
@ -938,22 +940,31 @@ the block fails to reject.
|
|||||||
(async () => {
|
(async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
async () => {
|
async () => {
|
||||||
throw new Error('Wrong value');
|
throw new TypeError('Wrong value');
|
||||||
},
|
},
|
||||||
Error
|
{
|
||||||
|
name: 'TypeError',
|
||||||
|
message: 'Wrong value'
|
||||||
|
}
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
assert.rejects(
|
assert.rejects(
|
||||||
() => Promise.reject(new Error('Wrong value')),
|
Promise.reject(new Error('Wrong value')),
|
||||||
Error
|
Error
|
||||||
).then(() => {
|
).then(() => {
|
||||||
// ...
|
// ...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that `error` cannot be a string. If a string is provided as the second
|
||||||
|
argument, then `error` is assumed to be omitted and the string will be used for
|
||||||
|
`message` instead. This can lead to easy-to-miss mistakes. Please read the
|
||||||
|
example in [`assert.throws()`][] carefully if using a string as the second
|
||||||
|
argument gets considered.
|
||||||
|
|
||||||
## assert.strictEqual(actual, expected[, message])
|
## assert.strictEqual(actual, expected[, message])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.21
|
added: v0.1.21
|
||||||
@ -1069,7 +1080,7 @@ assert.throws(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `error` can not be a string. If a string is provided as the second
|
Note that `error` cannot be a string. If a string is provided as the second
|
||||||
argument, then `error` is assumed to be omitted and the string will be used for
|
argument, then `error` is assumed to be omitted and the string will be used for
|
||||||
`message` instead. This can lead to easy-to-miss mistakes. Please read the
|
`message` instead. This can lead to easy-to-miss mistakes. Please read the
|
||||||
example below carefully if using a string as the second argument gets
|
example below carefully if using a string as the second argument gets
|
||||||
@ -1123,7 +1134,6 @@ second argument. This might lead to difficult-to-spot errors.
|
|||||||
[`assert.notDeepStrictEqual()`]: #assert_assert_notdeepstrictequal_actual_expected_message
|
[`assert.notDeepStrictEqual()`]: #assert_assert_notdeepstrictequal_actual_expected_message
|
||||||
[`assert.notStrictEqual()`]: #assert_assert_notstrictequal_actual_expected_message
|
[`assert.notStrictEqual()`]: #assert_assert_notstrictequal_actual_expected_message
|
||||||
[`assert.ok()`]: #assert_assert_ok_value_message
|
[`assert.ok()`]: #assert_assert_ok_value_message
|
||||||
[`assert.rejects()`]: #assert_assert_rejects_block_error_message
|
|
||||||
[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
|
[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
|
||||||
[`assert.throws()`]: #assert_assert_throws_block_error_message
|
[`assert.throws()`]: #assert_assert_throws_block_error_message
|
||||||
[`strict mode`]: #assert_strict_mode
|
[`strict mode`]: #assert_strict_mode
|
||||||
|
@ -34,7 +34,7 @@ const {
|
|||||||
}
|
}
|
||||||
} = require('internal/errors');
|
} = require('internal/errors');
|
||||||
const { openSync, closeSync, readSync } = require('fs');
|
const { openSync, closeSync, readSync } = require('fs');
|
||||||
const { inspect } = require('util');
|
const { inspect, types: { isPromise } } = require('util');
|
||||||
const { EOL } = require('os');
|
const { EOL } = require('os');
|
||||||
const { NativeModule } = require('internal/bootstrap/loaders');
|
const { NativeModule } = require('internal/bootstrap/loaders');
|
||||||
|
|
||||||
@ -440,13 +440,27 @@ function getActual(block) {
|
|||||||
return NO_EXCEPTION_SENTINEL;
|
return NO_EXCEPTION_SENTINEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkIsPromise(obj) {
|
||||||
|
// Accept native ES6 promises and promises that are implemented in a similar
|
||||||
|
// way. Do not accept thenables that use a function as `obj` and that have no
|
||||||
|
// `catch` handler.
|
||||||
|
return isPromise(obj) ||
|
||||||
|
obj !== null && typeof obj === 'object' &&
|
||||||
|
typeof obj.then === 'function' &&
|
||||||
|
typeof obj.catch === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForActual(block) {
|
async function waitForActual(block) {
|
||||||
if (typeof block !== 'function') {
|
let resultPromise;
|
||||||
throw new ERR_INVALID_ARG_TYPE('block', 'Function', block);
|
if (typeof block === 'function') {
|
||||||
|
// Return a rejected promise if `block` throws synchronously.
|
||||||
|
resultPromise = block();
|
||||||
|
} else if (checkIsPromise(block)) {
|
||||||
|
resultPromise = block;
|
||||||
|
} else {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE('block', ['Function', 'Promise'], block);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a rejected promise if `block` throws synchronously.
|
|
||||||
const resultPromise = block();
|
|
||||||
try {
|
try {
|
||||||
await resultPromise;
|
await resultPromise;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -485,7 +499,7 @@ function expectsError(stackStartFn, actual, error, message) {
|
|||||||
details += ` (${error.name})`;
|
details += ` (${error.name})`;
|
||||||
}
|
}
|
||||||
details += message ? `: ${message}` : '.';
|
details += message ? `: ${message}` : '.';
|
||||||
const fnType = stackStartFn === rejects ? 'rejection' : 'exception';
|
const fnType = stackStartFn.name === 'rejects' ? 'rejection' : 'exception';
|
||||||
innerFail({
|
innerFail({
|
||||||
actual: undefined,
|
actual: undefined,
|
||||||
expected: error,
|
expected: error,
|
||||||
@ -510,7 +524,8 @@ function expectsNoError(stackStartFn, actual, error, message) {
|
|||||||
|
|
||||||
if (!error || expectedException(actual, error)) {
|
if (!error || expectedException(actual, error)) {
|
||||||
const details = message ? `: ${message}` : '.';
|
const details = message ? `: ${message}` : '.';
|
||||||
const fnType = stackStartFn === doesNotReject ? 'rejection' : 'exception';
|
const fnType = stackStartFn.name === 'doesNotReject' ?
|
||||||
|
'rejection' : 'exception';
|
||||||
innerFail({
|
innerFail({
|
||||||
actual,
|
actual,
|
||||||
expected: error,
|
expected: error,
|
||||||
@ -523,29 +538,21 @@ function expectsNoError(stackStartFn, actual, error, message) {
|
|||||||
throw actual;
|
throw actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
function throws(block, ...args) {
|
assert.throws = function throws(block, ...args) {
|
||||||
expectsError(throws, getActual(block), ...args);
|
expectsError(throws, getActual(block), ...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
assert.throws = throws;
|
assert.rejects = async function rejects(block, ...args) {
|
||||||
|
|
||||||
async function rejects(block, ...args) {
|
|
||||||
expectsError(rejects, await waitForActual(block), ...args);
|
expectsError(rejects, await waitForActual(block), ...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
assert.rejects = rejects;
|
assert.doesNotThrow = function doesNotThrow(block, ...args) {
|
||||||
|
|
||||||
function doesNotThrow(block, ...args) {
|
|
||||||
expectsNoError(doesNotThrow, getActual(block), ...args);
|
expectsNoError(doesNotThrow, getActual(block), ...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
assert.doesNotThrow = doesNotThrow;
|
assert.doesNotReject = async function doesNotReject(block, ...args) {
|
||||||
|
|
||||||
async function doesNotReject(block, ...args) {
|
|
||||||
expectsNoError(doesNotReject, await waitForActual(block), ...args);
|
expectsNoError(doesNotReject, await waitForActual(block), ...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
assert.doesNotReject = doesNotReject;
|
|
||||||
|
|
||||||
assert.ifError = function ifError(err) {
|
assert.ifError = function ifError(err) {
|
||||||
if (err !== null && err !== undefined) {
|
if (err !== null && err !== undefined) {
|
||||||
|
@ -1,74 +1,116 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const { promisify } = require('util');
|
|
||||||
const wait = promisify(setTimeout);
|
|
||||||
|
|
||||||
/* eslint-disable prefer-common-expectserror, no-restricted-properties */
|
|
||||||
|
|
||||||
// Test assert.rejects() and assert.doesNotReject() by checking their
|
// Test assert.rejects() and assert.doesNotReject() by checking their
|
||||||
// expected output and by verifying that they do not work sync
|
// expected output and by verifying that they do not work sync
|
||||||
|
|
||||||
common.crashOnUnhandledRejection();
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
(async () => {
|
// Run all tests in parallel and check their outcome at the end.
|
||||||
await assert.rejects(
|
const promises = [];
|
||||||
async () => assert.fail(),
|
|
||||||
common.expectsError({
|
|
||||||
code: 'ERR_ASSERTION',
|
|
||||||
type: assert.AssertionError,
|
|
||||||
message: 'Failed'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await assert.doesNotReject(() => {});
|
// Check `assert.rejects`.
|
||||||
|
{
|
||||||
|
const rejectingFn = async () => assert.fail();
|
||||||
|
const errObj = {
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
|
message: 'Failed'
|
||||||
|
};
|
||||||
|
// `assert.rejects` accepts a function or a promise as first argument.
|
||||||
|
promises.push(assert.rejects(rejectingFn, errObj));
|
||||||
|
promises.push(assert.rejects(rejectingFn(), errObj));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const handler = (err) => {
|
||||||
|
assert(err instanceof assert.AssertionError,
|
||||||
|
`${err.name} is not instance of AssertionError`);
|
||||||
|
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
||||||
|
assert.strictEqual(err.message,
|
||||||
|
'Missing expected rejection (handler).');
|
||||||
|
assert.strictEqual(err.operator, 'rejects');
|
||||||
|
assert.ok(!err.stack.includes('at Function.rejects'));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise = assert.rejects(async () => {}, handler);
|
||||||
|
promises.push(assert.rejects(promise, handler));
|
||||||
|
|
||||||
|
promise = assert.rejects(() => {}, handler);
|
||||||
|
promises.push(assert.rejects(promise, handler));
|
||||||
|
|
||||||
|
promise = assert.rejects(Promise.resolve(), handler);
|
||||||
|
promises.push(assert.rejects(promise, handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const THROWN_ERROR = new Error();
|
||||||
|
|
||||||
|
promises.push(assert.rejects(() => {
|
||||||
|
throw THROWN_ERROR;
|
||||||
|
}).catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err, THROWN_ERROR);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(assert.rejects(
|
||||||
|
assert.rejects('fail', {}),
|
||||||
{
|
{
|
||||||
const promise = assert.doesNotReject(async () => {
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
await wait(1);
|
message: 'The "block" argument must be one of type ' +
|
||||||
throw new Error();
|
'Function or Promise. Received type string'
|
||||||
});
|
|
||||||
await assert.rejects(
|
|
||||||
() => promise,
|
|
||||||
(err) => {
|
|
||||||
assert(err instanceof assert.AssertionError,
|
|
||||||
`${err.name} is not instance of AssertionError`);
|
|
||||||
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
|
||||||
assert.strictEqual(err.message,
|
|
||||||
'Got unwanted rejection.\nActual message: ""');
|
|
||||||
assert.strictEqual(err.operator, 'doesNotReject');
|
|
||||||
assert.ok(!err.stack.includes('at Function.doesNotReject'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// Check `assert.doesNotReject`.
|
||||||
|
{
|
||||||
|
// `assert.doesNotReject` accepts a function or a promise as first argument.
|
||||||
|
promises.push(assert.doesNotReject(() => {}));
|
||||||
|
promises.push(assert.doesNotReject(async () => {}));
|
||||||
|
promises.push(assert.doesNotReject(Promise.resolve()));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const handler1 = (err) => {
|
||||||
|
assert(err instanceof assert.AssertionError,
|
||||||
|
`${err.name} is not instance of AssertionError`);
|
||||||
|
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
||||||
|
assert.strictEqual(err.message, 'Failed');
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const handler2 = (err) => {
|
||||||
|
assert(err instanceof assert.AssertionError,
|
||||||
|
`${err.name} is not instance of AssertionError`);
|
||||||
|
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
||||||
|
assert.strictEqual(err.message,
|
||||||
|
'Got unwanted rejection.\nActual message: "Failed"');
|
||||||
|
assert.strictEqual(err.operator, 'doesNotReject');
|
||||||
|
assert.ok(!err.stack.includes('at Function.doesNotReject'));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectingFn = async () => assert.fail();
|
||||||
|
|
||||||
|
let promise = assert.doesNotReject(rejectingFn, handler1);
|
||||||
|
promises.push(assert.rejects(promise, handler2));
|
||||||
|
|
||||||
|
promise = assert.doesNotReject(rejectingFn(), handler1);
|
||||||
|
promises.push(assert.rejects(promise, handler2));
|
||||||
|
|
||||||
|
promise = assert.doesNotReject(() => assert.fail(), common.mustNotCall());
|
||||||
|
promises.push(assert.rejects(promise, handler1));
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(assert.rejects(
|
||||||
|
assert.doesNotReject(123),
|
||||||
{
|
{
|
||||||
const promise = assert.rejects(() => {});
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
await assert.rejects(
|
message: 'The "block" argument must be one of type ' +
|
||||||
() => promise,
|
'Function or Promise. Received type number'
|
||||||
(err) => {
|
|
||||||
assert(err instanceof assert.AssertionError,
|
|
||||||
`${err.name} is not instance of AssertionError`);
|
|
||||||
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
|
||||||
assert(/^Missing expected rejection\.$/.test(err.message));
|
|
||||||
assert.strictEqual(err.operator, 'rejects');
|
|
||||||
assert.ok(!err.stack.includes('at Function.rejects'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
));
|
||||||
|
|
||||||
{
|
// Make sure all async code gets properly executed.
|
||||||
const THROWN_ERROR = new Error();
|
Promise.all(promises).then(common.mustCall());
|
||||||
|
|
||||||
await assert.rejects(() => {
|
|
||||||
throw THROWN_ERROR;
|
|
||||||
}).then(common.mustNotCall())
|
|
||||||
.catch(
|
|
||||||
common.mustCall((err) => {
|
|
||||||
assert.strictEqual(err, THROWN_ERROR);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})().then(common.mustCall());
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user