Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v8.x] backport assert: add rejects() and doesNotReject() #24019

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 80 additions & 0 deletions doc/api/assert.md
Expand Up @@ -242,6 +242,43 @@ If the values are not equal, an `AssertionError` is thrown with a `message`
property set equal to the value of the `message` parameter. If the `message`
parameter is undefined, a default error message is assigned.

## assert.doesNotReject(block[, error][, message])
<!-- YAML
added: REPLACEME
-->
* `block` {Function}
* `error` {RegExp|Function}
* `message` {any}

Awaits for the promise returned by function `block` to complete and not be
rejected. See [`assert.rejects()`][] for more details.

When `assert.doesNotReject()` is called, it will immediately call the `block`
function, and awaits for completion.

Besides the async nature to await the completion behaves identical to
[`assert.doesNotThrow()`][].

```js
(async () => {
await assert.doesNotReject(
async () => {
throw new TypeError('Wrong value');
},
SyntaxError
);
})();
```

```js
assert.doesNotReject(
() => Promise.reject(new TypeError('Wrong value')),
SyntaxError
).then(() => {
// ...
});
```

## assert.doesNotThrow(block[, error][, message])
<!-- YAML
added: v0.1.21
Expand Down Expand Up @@ -631,6 +668,48 @@ If the values are not strictly equal, an `AssertionError` is thrown with a
`message` property set equal to the value of the `message` parameter. If the
`message` parameter is undefined, a default error message is assigned.

## assert.rejects(block[, error][, message])
<!-- YAML
added: REPLACEME
-->
* `block` {Function}
* `error` {RegExp|Function|Object}
* `message` {any}

Awaits for promise returned by function `block` to be rejected.

When `assert.rejects()` is called, it will immediately call the `block`
function, and awaits for completion.

Besides the async nature to await the completion behaves identical to
[`assert.throws()`][].

If specified, `error` can be a constructor, [`RegExp`][], a validation
function, or an object where each property will be tested for.

If specified, `message` will be the message provided by the `AssertionError` if
the block fails to reject.

```js
(async () => {
await assert.rejects(
async () => {
throw new Error('Wrong value');
},
Error
);
})();
```

```js
assert.rejects(
() => Promise.reject(new Error('Wrong value')),
Error
).then(() => {
// ...
});
```

## assert.throws(block[, error][, message])
<!-- YAML
added: v0.1.21
Expand Down Expand Up @@ -786,6 +865,7 @@ For more information, see
[`assert.ok()`]: #assert_assert_ok_value_message
[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
[`assert.throws()`]: #assert_assert_throws_block_error_message
[`assert.rejects()`]: #assert_assert_rejects_block_error_message
[`strict mode`]: #assert_strict_mode
[Abstract Equality Comparison]: https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
Expand Down
68 changes: 53 additions & 15 deletions lib/assert.js
Expand Up @@ -718,17 +718,30 @@ function getActual(block) {
}
}

// Expected to throw an error.
assert.throws = function throws(block, error, message) {
const actual = getActual(block);
async function waitForActual(block) {
if (typeof block !== 'function') {
throw new errors.ERR_INVALID_ARG_TYPE('block', 'Function', block);
}

// Return a rejected promise if `block` throws synchronously.
const resultPromise = block();
try {
await resultPromise;
} catch (e) {
return e;
}
return errors.NO_EXCEPTION_SENTINEL;
}

// Expected to throw an error.
function expectsError(stackStartFn, actual, error, message) {
if (typeof error === 'string') {
if (arguments.length === 3)
if (arguments.length === 4) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'error',
['Function', 'RegExp'],
error);

}
message = error;
error = null;
}
Expand All @@ -739,21 +752,21 @@ assert.throws = function throws(block, error, message) {
details += ` (${error.name})`;
}
details += message ? `: ${message}` : '.';
const fnType = stackStartFn === rejects ? 'rejection' : 'exception';
innerFail({
actual,
expected: error,
operator: 'throws',
message: `Missing expected exception${details}`,
stackStartFn: throws
operator: stackStartFn.name,
message: `Missing expected ${fnType}${details}`,
stackStartFn
});
}
if (error && expectedException(actual, error, message) === false) {
throw actual;
}
};
}

assert.doesNotThrow = function doesNotThrow(block, error, message) {
const actual = getActual(block);
function expectsNoError(stackStartFn, actual, error, message) {
if (actual === undefined)
return;

Expand All @@ -764,16 +777,41 @@ assert.doesNotThrow = function doesNotThrow(block, error, message) {

if (!error || expectedException(actual, error)) {
const details = message ? `: ${message}` : '.';
const fnType = stackStartFn === doesNotReject ? 'rejection' : 'exception';
innerFail({
actual,
expected: error,
operator: 'doesNotThrow',
message: `Got unwanted exception${details}\n${actual.message}`,
stackStartFn: doesNotThrow
operator: stackStartFn.name,
message: `Got unwanted ${fnType}${details}\n${actual.message}`,
stackStartFn
});
}
throw actual;
};
}

function throws(block, ...args) {
expectsError(throws, getActual(block), ...args);
}

assert.throws = throws;

async function rejects(block, ...args) {
expectsError(rejects, await waitForActual(block), ...args);
}

assert.rejects = rejects;

function doesNotThrow(block, ...args) {
expectsNoError(doesNotThrow, getActual(block), ...args);
}

assert.doesNotThrow = doesNotThrow;

async function doesNotReject(block, ...args) {
expectsNoError(doesNotReject, await waitForActual(block), ...args);
}

assert.doesNotReject = doesNotReject;

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

Expand Down
73 changes: 73 additions & 0 deletions test/parallel/test-assert-async.js
@@ -0,0 +1,73 @@
'use strict';
const common = require('../common');
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
// expected output and by verifying that they do not work sync

common.crashOnUnhandledRejection();

(async () => {
await assert.rejects(
async () => assert.fail('Failed'),
common.expectsError({
code: 'ERR_ASSERTION',
type: assert.AssertionError,
message: 'Failed'
})
);

await assert.doesNotReject(() => {});

{
const promise = assert.doesNotReject(async () => {
await wait(1);
throw new Error();
});
await assert.rejects(
() => promise,
(err) => {
assert(err instanceof assert.AssertionError,
`${err.name} is not instance of AssertionError`);
assert.strictEqual(err.code, 'ERR_ASSERTION');
assert(/^Got unwanted rejection\.\n$/.test(err.message));
assert.strictEqual(err.operator, 'doesNotReject');
assert.ok(!err.stack.includes('at Function.doesNotReject'));
return true;
}
);
}

{
const promise = assert.rejects(() => {});
await assert.rejects(
() => promise,
(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;
}
);
}

{
const THROWN_ERROR = new Error();

await assert.rejects(() => {
throw THROWN_ERROR;
}).then(common.mustNotCall())
.catch(
common.mustCall((err) => {
assert.strictEqual(err, THROWN_ERROR);
})
);
}
})().then(common.mustCall());
11 changes: 11 additions & 0 deletions test/parallel/test-assert.js
Expand Up @@ -443,6 +443,7 @@ assert.throws(makeBlock(thrower, TypeError));
} catch (e) {
threw = true;
assert.ok(e instanceof a.AssertionError);
assert.ok(!e.stack.includes('at Function.doesNotThrow'));
}
assert.strictEqual(true, threw,
'a.doesNotThrow is not catching type matching errors');
Expand Down Expand Up @@ -544,6 +545,16 @@ a.throws(makeBlock(thrower, TypeError), (err) => {
code: 'ERR_ASSERTION',
message: /^Missing expected exception \(TypeError\): fhqwhgads$/
}));

let threw = false;
try {
a.throws(noop);
} catch (e) {
threw = true;
assert.ok(e instanceof a.AssertionError);
assert.ok(!e.stack.includes('at Function.throws'));
}
assert.ok(threw);
}

const circular = { y: 1 };
Expand Down