assert.rejects()

version added: 2.5.0.

Description

rejects( promise, message = "" )
rejects( promise, expectedMatcher, message = "" )

Test if the provided promise rejects, and optionally compare the rejection value.

name description
promise (thenable) Promise to test for rejection
expectedMatcher Rejection value matcher
message (string) Short description of the assertion

When testing code that is expected to return a rejected promise based on a specific set of circumstances, use assert.rejects() for testing and comparison.

The expectedMatcher argument can be:

  • A function that returns true when the assertion should be considered passing.
  • An Error object.
  • A base constructor, evaluated as rejectionValue instanceof expectedMatcher.
  • A RegExp that matches (or partially matches) rejectionValue.toString().

Note: in order to avoid confusion between the message and the expectedMatcher, the expectedMatcher can not be a string.

See also

Examples

Example: Catch async error

async function feedBaby (food) {
  if (food === 'sprouts') {
    throw new RangeError('Do not like');
  }
  return true;
}

QUnit.test('example', async function (assert) {
  await assert.rejects(
    feedBaby('sprouts'),
    RangeError
  );
});

Example: Matcher argument

QUnit.test('rejects example', function (assert) {
  // simple check
  assert.rejects(Promise.reject('some error'));

  // simple check
  assert.rejects(
    Promise.reject('some error'),
    'optional description here'
  );

  // match pattern on actual error
  assert.rejects(
    Promise.reject(new Error('some error')),
    /some error/,
    'optional description here'
  );

  // Using a custom error constructor
  function CustomError (message) {
    this.message = message;
  }
  CustomError.prototype.toString = function () {
    return this.message;
  };

  // actual error is an instance of the expected constructor
  assert.rejects(
    Promise.reject(new CustomError('some error')),
    CustomError
  );

  // actual error has strictly equal `constructor`, `name` and `message` properties
  // of the expected error object
  assert.rejects(
    Promise.reject(new CustomError('some error')),
    new CustomError('some error')
  );

  // custom validation arrow function
  assert.rejects(
    Promise.reject(new CustomError('some error')),
    (err) => err.toString() === 'some error'
  );

  // custom validation function
  assert.rejects(
    Promise.reject(new CustomError('some error')),
    function (err) {
      return err.toString() === 'some error';
    }
  );
});

Example: Await the assertion

When you pass a Promise (or async function call) to assert.rejects(), QUnit automatically tracks this as part of your test. This tracking is similar to what would happen if you manually passed assert.async() to promise.finally(). This means the test is not complete until the passed Promise is also settled, and this means timeouts naturally apply as well.

However, if two parts of your test code or subject share state within a single test, you may want to await the assertion to prevent the rest of your test from running at the same time.

To make this easy, the assert.rejects() method itself can be awaited, which will wait for the given async function call or other Promise.

QUnit.test('stateful example', async function (assert) {
  let value;

  async function feedMe () {
    await Promise.resolve();
    if (value === 1) {
      throw new Error('I want more');
    }
    throw new Error('Thank you');
  }

  value = 5;
  // if we don't await, then this test will fail,
  // because `value = 1` would run too soon.
  await assert.rejects(feedMe(), /Thank you/);

  value = 1;
  await assert.rejects(feedMe(), /I want more/);
});

Example: Workarounds

Avoid using error handling callbacks, such as Promise.catch or on('error'), because:

  • Your test is likely to silently pass even if the expected error does not happen, or if the callback is lost or otherwise not invoked for some reason.
  • When an error happens, but not the error you expect, the actual value is not visible in your CI output.
QUnit.test('BAD example', function (assert) {
  return feedBaby('sprouts')
    .catch((e) => {
      assert.true(e instanceof RangeError);
    });
});

Avoid manually tracking rejections with assert.async(), because:

  • When you put assertions inside a callback, especially negative assertions, the test function is no longer in control over the assertions. This means the test now only works as intended if the callback is called exactly as it should. If the callback isn’t called, the test may succeed or fail/timeout in away that are hard to diagnose.
  • When a different error happens, the actual value is not visible in your CI output.
  • This requires writing more boilerplate, which is easy to get wrong and ways that don’t self-correct (i.e. pass even when it shouldn’t).
QUnit.test('BAD example', function (assert) {
  const done = assert.async();

  feedBaby('sprouts')
    .then(() => {
      assert.true(false, 'should have failed');
    })
    .catch((e) => {
      assert.true(e instanceof RangeError);
    })
    .finally(done);
});

If you want to perform additional assertions after a failure, consider performing these directly in your test function, after assert.rejects():

QUnit.test('example', async function (assert) {
  const p = feedMe();
  await assert.rejects(p, RangeError);

  try {
    await p;
  } catch (e) {
    assert.deepEqual(e.somedata, { foo: 'bar' });
  }
});