Skip to content

Commit

Permalink
assert: add rejects() and doesNotReject()
Browse files Browse the repository at this point in the history
Implement asynchronous equivalent for assert.throws() and
assert.doesNotThrow().

Backport-PR-URL: #24019
PR-URL: #18023
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Shingo Inoue <leko.noor@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
  • Loading branch information
feugy authored and MylesBorins committed Nov 4, 2018
1 parent 7f34c27 commit 3babc5b
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 15 deletions.
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
65 changes: 50 additions & 15 deletions lib/assert.js
Expand Up @@ -718,17 +718,27 @@ 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);
}
try {
await block();
} 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 +749,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 +774,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
66 changes: 66 additions & 0 deletions test/parallel/test-assert-async.js
@@ -0,0 +1,66 @@
'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

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

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

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

{
const promise = assert.doesNotReject(async () => {
await wait(1);
throw new Error();
});
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(() => {});
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;
}
);
}
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

0 comments on commit 3babc5b

Please sign in to comment.