39

I want to check if an async function throws using assert.throws from the native assert module. I tried with

const test = async () => await aPromise();
assert.throws(test); // AssertionError: Missing expected exception..

It (obviously?) doesn't work because the function exits before the Promise is resolved. Yet I found this question where the same thing is attained using callbacks.

Any suggestion?

(I'm transpiling to Node.js native generators using Babel.)

Pang
  • 9,564
  • 146
  • 81
  • 122
seldon
  • 977
  • 2
  • 8
  • 20

5 Answers5

50

node 10 and newer

Since Node.js v10.0, there is assert.rejects which does just that.

Older versions of node

async functions never throw - they return promises that might be rejected.

You cannot use assert.throws with them. You need to write your own asynchronous assertion:

async function assertThrowsAsynchronously(test, error) {
    try {
        await test();
    } catch(e) {
        if (!error || e instanceof error)
            return "everything is fine";
    }
    throw new AssertionError("Missing rejection" + (error ? " with "+error.name : ""));
}

and use it like

return assertThrowsAsynchronously(aPromise);

in an asynchronous test case.

Pang
  • 9,564
  • 146
  • 81
  • 122
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • This is un-necessary as `assert.throws` already returns a promise which behaves exactly like this – Ali Jun 13 '18 at 12:12
  • @Ali Which `assert.throws` are you using? The [native node.js one](https://nodejs.org/api/assert.html#assert_assert_throws_block_error_message) neither returns a promise nor copes with a function that returns a promise – Bergi Jun 13 '18 at 12:17
  • It's the native one. In node v8 at least. It's not documented but that's what it's doing. – Ali Jun 13 '18 at 12:40
  • @Ali [No, it doesn't](https://github.com/BridgeAR/node/blob/a5d86f8c4ebf201092a7a155691856e13639f0cb/lib/assert.js#L611)? – Bergi Jun 13 '18 at 12:59
  • 2
    yes it doesn't. Sorry for the confusion. We are using `assert-throws-async` from `npm` and replacing native `assert.throws`. I missed it as the original was replaced in a different file and it worked without having to re-import `assert-throws-async` in my file. – Ali Jun 13 '18 at 15:14
21

Based on Bergi answer I've suggest more universal solution that utilizes original assert.throws for error messages:

import assert from 'assert';

async function assertThrowsAsync(fn, regExp) {
  let f = () => {};
  try {
    await fn();
  } catch(e) {
    f = () => {throw e};
  } finally {
    assert.throws(f, regExp);
  }
}

Usage:

it('should throw', async function () {
    await assertThrowsAsync(async () => await asyncTask(), /Error/);
});
vitalets
  • 4,675
  • 1
  • 35
  • 35
3

The answers given work, but I came across this issue today and came up with another solution, that I think is a little simpler.

// Code being tested
async function thisFunctionThrows() {
    throw new Error('Bad response')
}


// In your test.
try {
    await thisFunctionThrows()
    assert.equal(1 == 0) // Never gets run. But if it does you know it didn't throw.
} catch (e) {
    assert(e.message.includes('Bad response'))
}
jayWHY
  • 980
  • 7
  • 6
3

Since the question is still getting attention, I'd like to sum up the two best solutions, especially to highlight the new standard method.

Node v10+

There's a dedicated method in the assert library, assert.rejects.

For older versions of Node

A fill from vitalets answer:

import assert from 'assert';

async function assertThrowsAsync(fn, regExp) {
  let f = () => {};
  try {
    await fn();
  } catch(e) {
    f = () => {throw e};
  } finally {
    assert.throws(f, regExp);
  }
}
seldon
  • 977
  • 2
  • 8
  • 20
0

You are going to want to use, assert.rejects() which is new in Node.js version 10.

At the high level, instead of assert.throws, we want something like assert.rejects, hopefully you can take this and run with it:

        const assertRejects = (fn, options) => {
            return Promise.resolve(fn()).catch(e => {
                    return {
                        exception: e,
                        result: 'OK'
                    }
                })
                .then(v => {

                    if (!(v && v.result === 'OK')) {
                        return Promise.reject('Missing exception.');
                    }

                    if (!options) {
                        return;
                    }

                    if (options.message) {
                        // check options
                    }

                    console.log('here we check options');

                });
        };

        it('should save with error', async () => {

            // should be an error because of duplication of unique document (see indexes in the model)
            return await assertRejects(async () => {

                patientSubscriber = await PatientSubscriber.create({
                    isSubscribed: true,
                    patient: patient._id,
                    subscriber: user._id
                });

            }, {
                message: /PatientSubscriber validation failed/
            });

        });
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817