assert.throws()
Runs a function and throws an AssertionError unless it threw an error matching the expectation.
assert.throws(fn[, error][, message])- Added in Node.js v0.1.21
Parameters
| Parameter | Type | Description |
|---|---|---|
$fn | Function | The function to invoke; it is called immediately and synchronously. |
$erroroptional | RegExp | Function | Object | Error | What the thrown error must match: a constructor, a regular expression tested against the stringified error, a validation function returning true, or an object whose properties are each compared. |
$messageoptional | string | Message used by the AssertionError when fn does not throw at all. |
Errors
Throws an AssertionError with code ERR_ASSERTION when fn does not throw, or when the thrown error does not match error. Passing a string as the second argument when fn throws a matching Error raises ERR_AMBIGUOUS_ARGUMENT.
Changelog
- PHP v10.2.0The error parameter can be an object containing regular expressions.
- PHP v9.9.0The error parameter can now be an object as well.
- PHP v4.2.0The error parameter can now be an arrow function.
assert.throws() calls fn immediately and requires it to throw. Everything interesting is in the second argument, which can take four shapes:
- A constructor —
assert.throws(fn, TypeError)checksinstanceof. - A RegExp — tested against the stringified error, i.e.
'TypeError: bad port', so it can match the name as well as the message. - An object — each property is compared against the thrown error, and a property whose value is a RegExp is tested rather than compared. Properties you leave out are not checked.
- A validation function — receives the error and must return
true.
Never pass a bare string as the second argument
A string there is the message, not a matcher. If fn throws an error whose message equals that string, Node cannot tell what you meant and raises ERR_AMBIGUOUS_ARGUMENT; if the messages differ, the assertion silently passes without checking anything. Use /pattern/ or { message: 'text' } instead.
Pass a function, not a call
The argument must be () => parse(input). Writing assert.throws(parse(input)) runs the call first, so the error escapes before assert.throws ever sees it. For anything asynchronous, use assert.rejects().
Example
When to use it
- Assert a validator rejects malformed input with the right error class
- Pin an error's code property so a caller's catch block keeps working
- Check that a required option missing from a config throws rather than defaulting silently
Frequently asked questions
Why does assert.throws() pass even though my function threw the wrong error?
You probably passed a plain string as the second argument, which Node treats as the assertion message rather than an error matcher, so nothing about the thrown error is checked. Use a RegExp, a constructor, or an object such as { message: 'text' }.
How do I check the error message with assert.throws()?
Pass a RegExp — tested against the stringified error, for example /must be an integer/ — or an object like { message: 'port must be an integer' } for an exact match. The object form also lets you pin name, code and any custom property in the same call.
Why does assert.throws() not catch my async function's error?
An async function returns a rejected promise instead of throwing synchronously, so assert.throws sees a normal return value and reports "Missing expected exception". Use await assert.rejects() instead.
More examples
The string-argument trap
The first call is the dangerous one: it passes even though the thrown message is 'First', because the string was consumed as the assertion message rather than as a matcher. The last two lines show the two forms that really compare the message, and the regexp is tested against the full 'Error: First' string, not just the message.
const assert = require('node:assert');
function throwingFirst() { throw new Error('First'); }
function throwingSecond() { throw new Error('Second'); }
function notThrowing() { return 1; }
// Passes without checking anything — 'Second' was taken as the message.
assert.throws(throwingFirst, 'Second');
console.log('vacuous pass: the thrown message was actually "First"');
// When the message DOES match, Node refuses to guess.
try {
assert.throws(throwingSecond, 'Second');
} catch (err) {
console.log(err.constructor.name, err.code); // TypeError ERR_AMBIGUOUS_ARGUMENT
}
// The string is only ever used when the function fails to throw.
try {
assert.throws(notThrowing, 'Second');
} catch (err) {
console.log(err.message); // Missing expected exception: Second
}
// The forms that actually check the message:
assert.throws(throwingFirst, /^Error: First$/);
assert.throws(throwingFirst, { message: 'First' });
console.log('regexp and object forms both matched');Matching an error's code and cause
The object form mixes exact values with a RegExp for the message, and properties absent from the expectation (dsn) are not checked at all. The predicate trick in the second half captures the error so you can assert on nested fields such as cause, which the object form compares only by deep equality.
const assert = require('node:assert');
function connect(url) {
if (!url.startsWith('postgres://')) {
const err = new Error(`unsupported driver in ${url}`, {
cause: new TypeError('only postgres:// is supported'),
});
err.code = 'ERR_BAD_DSN';
err.dsn = url;
throw err;
}
}
// Only the listed properties are checked — err.dsn is ignored here.
assert.throws(() => connect('mysql://localhost'), {
name: 'Error',
code: 'ERR_BAD_DSN',
message: /unsupported driver/, // a RegExp value is tested, not compared
});
// Capture the error yourself when you need richer checks.
let caught;
assert.throws(() => connect('mysql://localhost'), (err) => { caught = err; return true; });
console.log(caught.code, '|', caught.cause.constructor.name);
// ERR_BAD_DSN | TypeErrorInside the built-in test runner
Both failure paths throw a RangeError with the same code, so matching on the class alone would not distinguish them; pinning the message as well is what makes the two assertions test different branches. The happy path is asserted first so a totally broken parser fails on the value rather than passing both throw checks.
const test = require('node:test');
const assert = require('node:assert/strict');
function parseRange(header) {
const m = /^bytes=(\d+)-(\d+)$/.exec(header ?? '');
if (!m) throw Object.assign(new RangeError('malformed Range header'), { code: 'ERR_RANGE' });
const [, start, end] = m.map(Number);
if (start > end) throw Object.assign(new RangeError('start after end'), { code: 'ERR_RANGE' });
return { start, end };
}
test('parseRange rejects bad input', () => {
assert.deepStrictEqual(parseRange('bytes=0-99'), { start: 0, end: 99 });
assert.throws(() => parseRange(undefined), { code: 'ERR_RANGE', message: 'malformed Range header' });
assert.throws(() => parseRange('bytes=99-0'), { code: 'ERR_RANGE', message: 'start after end' });
});
// $ node --test
// ✓ parseRange rejects bad input (1.0ms)
Discussion