assert.rejects()

Awaits a promise or async function and fails unless it rejects with a matching error.

Signature
assert.rejects(asyncFn[, error][, message])
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$asyncFnFunction | PromiseA promise to await, or a function that is called immediately and whose returned promise is awaited.
$erroroptionalRegExp | Function | Object | ErrorWhat the rejection reason must match: a class, a regular expression, a validation function, or an object whose properties are each tested.
$messageoptionalstringMessage 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · javascript
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.

Example · javascript
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.

Example · javascript
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

  • Be the first to comment on this lesson.