assert
assert.ok(value[, message]) Importing assert
The node:assert module is built into Node and needs no install. Three import patterns are available:
// legacy mode (not recommended for new code)
import assert from 'node:assert';
// strict mode (recommended)
import { strict as assert } from 'node:assert';
// or, equivalently since v15.0.0:
import assert from 'node:assert/strict';
Use node:assert/strict for new code. Legacy mode’s loose == produces surprises, and every recent Node release recommends the strict surface.
Strict and legacy mode
Only four methods behave differently between the two modes:
| Method | Legacy (default) | Strict |
|---|---|---|
assert.equal | == | alias of assert.strictEqual |
assert.notEqual | != | alias of assert.notStrictEqual |
assert.deepEqual | recursive == | alias of assert.deepStrictEqual |
assert.notDeepEqual | recursive != | alias of assert.notDeepStrictEqual |
The legacy == semantics create footguns. The canonical example passes silently in legacy mode but throws in strict mode:
// legacy mode
import assert from 'node:assert';
assert.deepEqual('+00000000', false); // OK ('' == false under loose coercion)
assert.equal(1, '1'); // OK
assert.equal(NaN, NaN); // OK (specially handled)
Re-run the same calls in strict mode and every one of them throws at the assertion boundary. Loose equality, the NaN self-equality special case, and the recursive string-to-boolean coercion that legacy mode permits are all gone the moment the import is switched to node:assert/strict:
// strict mode
import { strict as assert } from 'node:assert';
assert.deepEqual('+00000000', false); // AssertionError
assert.equal(1, '1'); // AssertionError
In strict mode assert.strictEqual uses Object.is(). That gives NaN === NaN but keeps +0 and -0 distinct, which is the opposite of === for the signed zeros. Default to strict.
AssertionError
Every failed assertion throws an assert.AssertionError, a subclass of Error. The constructor accepts an options object:
new assert.AssertionError({
message: 'inputs differ',
actual: 1,
expected: 2,
operator: 'strictEqual',
stackStartFn: myFn, // omit earlier frames from the stack
diff: 'full', // 'simple' (default) or 'full'
});
Instances expose actual, expected, operator, and generatedMessage. The code property is always 'ERR_ASSERTION'. Standard Error.message and Error.name are also present. Catch the error with instanceof assert.AssertionError or by checking err.code === 'ERR_ASSERTION'.
Assertion methods
assert.ok(value[, message])
Throws if value is falsy. Added in v0.1.21. Since v10.0.0, calling it with no args throws No value argument passed to assert.ok(). The shorter assert(value) is the same function.
import { strict as assert } from 'node:assert';
assert.ok(2 + 2 === 4); // OK
assert.ok(0); // AssertionError
assert.strictEqual and assert.equal
assert.strictEqual(actual, expected[, message]) compares with Object.is(). The loose assert.equal uses == in legacy mode and only matches the strict version when you import from node:assert/strict.
import { strict as assert } from 'node:assert';
assert.strictEqual(NaN, NaN); // OK (Object.is)
assert.strictEqual(0, -0); // AssertionError
assert.deepStrictEqual and assert.deepEqual
Compares own-enumerable properties. The strict form uses Object.is() for primitives (so 0 and -0 differ), checks symbol-keyed properties, and compares prototypes. The loose form uses recursive ==. WeakMap, WeakSet, and Promise are always compared by reference, even in deep strict mode. This is deliberate: two WeakSets holding the same items are not deep-equal.
import { strict as assert } from 'node:assert';
assert.deepStrictEqual({ a: 1 }, { a: 1 }); // OK
assert.deepStrictEqual({ a: 1 }, { a: '1' }); // AssertionError
assert.partialDeepStrictEqual(actual, expected[, message])
Stabilized in v24.0.0. The expected only needs to specify a subset of properties; extra properties on actual are ignored. Useful when you care about a few fields and don’t want to list every one of them.
import { strict as assert } from 'node:assert';
assert.partialDeepStrictEqual(
{ a: 1, b: 2, c: 3 },
{ b: 2 },
); // OK
assert.match and assert.doesNotMatch
Added in v13.6.0, no longer experimental since v16.0.0. The first argument must be a string or the call throws ERR_ASSERTION. Use match to assert a string contains a pattern, and doesNotMatch for the negative case. Both fit well into testing flows and input-validation guards where you only care about a substring of a larger message.
import { strict as assert } from 'node:assert';
assert.match('hello world', /world/); // OK
assert.doesNotMatch('hello', /world/); // OK
assert.throws and assert.rejects
assert.throws(fn[, error][, message]) runs fn and asserts it throws. The optional error argument can be a class (checked with instanceof), a RegExp (matched against String(err), which includes name), a validator function returning true, or an object whose properties must deep-strict-equal the thrown error. A string in this slot is treated as message, not a matcher, and triggers ERR_AMBIGUOUS_ARGUMENT when the thrown message happens to match the string.
assert.rejects(asyncFn[, error][, message]) is the async twin and returns a Promise. asyncFn can be a function or a Promise directly, so you can pass a rejected promise without an IIFE wrapper.
import { strict as assert } from 'node:assert';
assert.throws(
() => {
throw new TypeError('Wrong value');
},
{ name: 'TypeError', message: /Wrong/ },
);
await assert.rejects(Promise.reject(new Error('boom')), /boom/);
assert.fail and assert.ifError
assert.fail([message]) throws an AssertionError with the default message 'Failed'. Passing more than one argument to assert.fail() is deprecated.
assert.ifError(value) throws if value is anything other than undefined or null. It is designed for the err argument in Node-style callbacks, so you can write assert.ifError(err); and have it throw the moment a callback receives a real error.
Custom Assert instances
new assert.Assert({ diff: 'full', strict: true, skipPrototype: false }), added in v24.6.0 (and v22.19.0), returns an isolated assertion object with its own options. Use it when you want 'full' diffs or skipPrototype behavior without flipping the entire module to strict mode. Note the version requirement: Node 18 LTS does not have this class.
A subtle gotcha: destructuring methods off an Assert instance drops the configuration. Methods become unbound and revert to default behavior (diff: 'simple', non-strict). Always call methods on the instance directly.
import { Assert } from 'node:assert';
const assertInstance = new Assert({ diff: 'full' });
assertInstance.deepStrictEqual({ a: 1 }, { a: 2 });
// Multi-line diff is enabled by 'full'
Message parameter
Every method that accepts a message accepts three forms:
- A
string, used as-is. Extra arguments after the message go throughutil.format()and are substituted with printf-like tokens (%s,%d,%i,%f,%j,%o,%O,%%). - An
Error, thrown directly. If you also pass extra args, Node throwsERR_AMBIGUOUS_ARGUMENT. - A
Functionof the form(actual, expected) => string, called only on failure. A non-string return falls back to the default message. Extra args plus a function also raiseERR_AMBIGUOUS_ARGUMENT.
Anything else raises ERR_INVALID_ARG_TYPE.
import { strict as assert } from 'node:assert';
const got = 1;
const want = 2;
assert.strictEqual(got, want, 'got %d, want %d', got, want);
// AssertionError [ERR_ASSERTION]: got 1, want 2
assert.strictEqual(1, 2, new TypeError('Inputs are not identical'));
// TypeError: Inputs are not identical
Disable diff colors with NO_COLOR=1 or NODE_DISABLE_COLORS=1 when running in a terminal that mishandles ANSI escapes.
Common patterns
The module is a Node.js builtin, and the strict surface fits naturally into both runtime argument checks and unit testing in plain JavaScript. A few patterns come up often.
Argument validation
For argument checks at the top of a function, throw early and skip the rest of the body. Every function that takes untrusted input can use the same set of checks to fail loudly with structured errors, which is usually easier to debug than letting a TypeError surface from three frames down the call stack.
import { strict as assert } from 'node:assert';
function divide(numerator, denominator) {
assert.ok(Number.isFinite(numerator), 'numerator must be finite');
assert.strictEqual(typeof denominator, 'number', 'denominator must be a number');
assert.notStrictEqual(denominator, 0, 'denominator must not be zero');
return numerator / denominator;
}
Testing code
In testing code, assert is the foundation that frameworks like node:test and Vitest layer their own matchers on top of. The same calls work without a runner when you need a quick check in a script.
import { strict as assert } from 'node:assert';
import { test } from 'node:test';
test('divide handles non-zero denominators', async () => {
assert.strictEqual(divide(10, 2), 5);
await assert.rejects(() => divide(1, 0), /zero/);
});
Snapshots and partial matches
For state-machine or builder-style code, assert.deepStrictEqual on a snapshot of the object after a sequence of operations catches accidental shape changes that property-by-property checks would miss. assert.partialDeepStrictEqual works when you only care about a few of the fields and the rest can drift.
See also
/reference/node-modules/process/— forassert.ifErrorin callback-style code/reference/built-in-objects/error-constructor/— base class forAssertionError/reference/global-functions/queue-microtask/— useful for scheduling work afterassert.rejects