assert.rejects()
Awaits a promise or async function and fails unless it rejects with a matching error.
assert.rejects(asyncFn[, error][, message])- Added in Node.js v10.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$asyncFn | Function | Promise | A promise to await, or a function that is called immediately and whose returned promise is awaited. |
$erroroptional | RegExp | Function | Object | Error | What the rejection reason must match: a class, a regular expression, a validation function, or an object whose properties are each tested. |
$messageoptional | string | Message used by the AssertionError when asyncFn does not reject. |
Return value
PromiseA promise that fulfils when asyncFn rejects with a matching error, and rejects with an AssertionError otherwise.
Errors
Returns a rejected promise carrying an AssertionError when asyncFn fulfils instead of rejecting, or when the rejection reason does not match error. If asyncFn is a function returning a non-promise, the result rejects with ERR_INVALID_RETURN_VALUE.
assert.rejects() is the asynchronous twin of assert.throws(). Aside from the await, the matching rules are identical: the second argument may be an error class, a RegExp tested against the stringified rejection reason, a validation function, or an object whose listed properties are each compared.
Always await it
This is the mistake that costs people an afternoon. assert.rejects() returns a promise and signals failure by rejecting it. Forget the await and a failing assertion becomes an unhandled rejection: on modern Node that crashes the process with a confusing stack, and inside a test runner it may land against a different test entirely — or the test finishes green before the check has run at all.
Function or promise
Both assert.rejects(fn()) and assert.rejects(() => fn()) work. The function form is safer with code that can throw synchronously before returning a promise, because assert.rejects catches that too. If the function returns something that is not a promise, you get ERR_INVALID_RETURN_VALUE rather than a pass.
Not for every failure
A rejected promise is not the only async failure mode; error-first callbacks report through their first argument, which is assert.ifError()'s job instead.
Example
When to use it
- Assert an async database helper rejects with ERR_NOT_FOUND for a missing id
- Check that fs.promises.readFile rejects with ENOENT for a path that does not exist
- Verify a retry wrapper gives up and rejects after the configured number of attempts
Frequently asked questions
Do I need to await assert.rejects()?
Yes, always. It returns a promise and reports failure by rejecting it, so an un-awaited call turns a failed assertion into an unhandled rejection that either crashes the process or is blamed on an unrelated test.
What is the difference between assert.throws() and assert.rejects()?
assert.throws() calls a function and expects a synchronous throw; assert.rejects() awaits a promise or an async function and expects a rejection. The error-matching argument behaves identically in both.
Can I pass a promise directly to assert.rejects()?
Yes — asyncFn accepts either a Promise or a function. The function form is slightly safer because it also captures an error thrown synchronously before the promise is created; a function returning a non-promise rejects with ERR_INVALID_RETURN_VALUE.
More examples
The missing await
badTest prints a success line even though its assertion failed, because the rejected promise it created is never awaited — on a real run that surfaces later as an unhandled rejection with a stack pointing nowhere useful. Only the awaited call converts the failure into an error the caller can see.
import assert from 'node:assert';
async function alwaysResolves() { return 'ok'; }
// WRONG: the assertion fails, but nothing here observes it.
function badTest() {
assert.rejects(alwaysResolves()); // returns a rejected promise, dropped on the floor
console.log('badTest "passed"');
}
// RIGHT: awaiting turns the failure into a thrown AssertionError.
async function goodTest() {
await assert.rejects(alwaysResolves());
}
badTest();
try {
await goodTest();
} catch (err) {
console.log('goodTest failed as it should:', err.code, '|', err.message);
}
// badTest "passed"
// goodTest failed as it should: ERR_ASSERTION | Missing expected rejection.Asserting a real filesystem rejection
The first test pins three properties of the ENOENT error at once, which is stronger than matching the message because the message text includes the path and varies. The second uses a predicate because the errno differs between platforms, and cleans the directory up in a finally so a failing assertion still leaves the working tree tidy.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
test('readFile rejects for a missing path', async () => {
await assert.rejects(
fs.readFile('./definitely-not-here.txt', 'utf8'),
{ code: 'ENOENT', name: 'Error', syscall: 'open' },
);
});
test('writeFile rejects on a directory path', async () => {
await fs.mkdir('./tmp-dir', { recursive: true });
try {
await assert.rejects(
fs.writeFile('./tmp-dir', 'nope'),
(err) => ['EISDIR', 'EPERM'].includes(err.code),
);
} finally {
await fs.rmdir('./tmp-dir');
}
});
// $ node --test
// ✓ readFile rejects for a missing path (1.4ms)
// ✓ writeFile rejects on a directory path (2.1ms)Checking a retry wrapper gives up
The validation function is used as a place to run further assertions on the rejection reason, including the cause chain, and returns true to signal a match. The call counter afterwards proves the wrapper actually retried rather than failing once — a check the rejection assertion alone cannot make.
import assert from 'node:assert';
async function withRetries(fn, attempts) {
let last;
for (let i = 0; i < attempts; i++) {
try {
return await fn(i);
} catch (err) {
last = err;
}
}
throw new Error(`gave up after ${attempts} attempts`, { cause: last });
}
let calls = 0;
const alwaysFails = async () => {
calls++;
throw Object.assign(new Error('upstream 503'), { code: 'ERR_UPSTREAM' });
};
await assert.rejects(withRetries(alwaysFails, 3), (err) => {
assert.strictEqual(err.message, 'gave up after 3 attempts');
assert.strictEqual(err.cause.code, 'ERR_UPSTREAM');
return true;
});
console.log('attempts made:', calls); // attempts made: 3
Discussion