Back to Blog
Node.jsAssertTestingBackend

Node.js Assert Module Guide

Master Node.js assertions for testing and validation.

B
Bootspring Team
Engineering
July 19, 2018
7 min read

The assert module provides functions for verifying invariants and writing tests.

Basic Assertions#

1import assert from 'assert'; 2 3// assert(value) - truthy check 4assert(true); // OK 5assert(1); // OK 6assert('hello'); // OK 7// assert(false); // AssertionError 8// assert(0); // AssertionError 9// assert(''); // AssertionError 10 11// With custom message 12assert(value, 'Value must be truthy'); 13 14// assert.ok() - same as assert() 15assert.ok(true); 16assert.ok(1 > 0, 'One should be greater than zero');

Equality Assertions#

1import assert from 'assert'; 2 3// Strict equality (===) 4assert.strictEqual(1, 1); // OK 5assert.strictEqual('a', 'a'); // OK 6// assert.strictEqual(1, '1'); // AssertionError 7 8// Loose equality (==) 9assert.equal(1, '1'); // OK (coercion) 10assert.equal(null, undefined); // OK 11 12// Not equal 13assert.notStrictEqual(1, 2); // OK 14assert.notStrictEqual(1, '1'); // OK 15assert.notEqual(1, 2); // OK 16 17// Deep equality (objects/arrays) 18assert.deepStrictEqual( 19 { a: 1, b: { c: 2 } }, 20 { a: 1, b: { c: 2 } } 21); // OK 22 23assert.deepStrictEqual([1, 2, 3], [1, 2, 3]); // OK 24 25// Deep not equal 26assert.notDeepStrictEqual( 27 { a: 1 }, 28 { a: 2 } 29); // OK

Strict vs Legacy Mode#

1import assert from 'assert/strict'; 2// Or: import { strict as assert } from 'assert'; 3 4// In strict mode: 5// - assert.equal becomes assert.strictEqual 6// - assert.deepEqual becomes assert.deepStrictEqual 7// - assert.notEqual becomes assert.notStrictEqual 8 9assert.equal(1, 1); // Uses strictEqual 10// assert.equal(1, '1'); // AssertionError in strict mode 11 12// Always prefer strict mode for new code 13import { strict as assert } from 'assert'; 14 15assert.deepEqual({ a: 1 }, { a: 1 }); // Uses deepStrictEqual

Error Assertions#

1import { strict as assert } from 'assert'; 2 3// assert.throws - sync function throws 4assert.throws( 5 () => { throw new Error('Wrong!'); }, 6 Error 7); 8 9// With message matcher 10assert.throws( 11 () => { throw new Error('Invalid input'); }, 12 /Invalid/ 13); 14 15// With error object 16assert.throws( 17 () => { throw new Error('Not found'); }, 18 { 19 name: 'Error', 20 message: 'Not found' 21 } 22); 23 24// With custom validation 25assert.throws( 26 () => { throw new TypeError('Type error'); }, 27 (err) => { 28 assert.strictEqual(err.name, 'TypeError'); 29 assert.strictEqual(err.message, 'Type error'); 30 return true; // Must return true 31 } 32); 33 34// assert.doesNotThrow 35assert.doesNotThrow( 36 () => { return 42; }, 37 'Should not throw' 38);

Async Assertions#

1import { strict as assert } from 'assert'; 2 3// assert.rejects - async function rejects 4await assert.rejects( 5 async () => { throw new Error('Async error'); }, 6 Error 7); 8 9await assert.rejects( 10 async () => { throw new Error('Failed'); }, 11 { 12 name: 'Error', 13 message: 'Failed' 14 } 15); 16 17// With promise 18await assert.rejects( 19 Promise.reject(new Error('Rejected')), 20 /Rejected/ 21); 22 23// assert.doesNotReject 24await assert.doesNotReject( 25 async () => { return 'success'; } 26); 27 28await assert.doesNotReject( 29 Promise.resolve('value') 30);

Pattern Matching#

1import { strict as assert } from 'assert'; 2 3// assert.match - regex matching 4assert.match('hello world', /world/); 5assert.match('test@example.com', /^\S+@\S+\.\S+$/); 6 7// assert.doesNotMatch 8assert.doesNotMatch('hello', /world/); 9assert.doesNotMatch('abc', /\d+/); 10 11// Object pattern matching 12const user = { 13 id: 1, 14 name: 'John', 15 email: 'john@example.com', 16 createdAt: new Date() 17}; 18 19// Partial match with properties 20assert.deepStrictEqual( 21 { id: user.id, name: user.name }, 22 { id: 1, name: 'John' } 23); 24 25// Custom matcher 26function assertMatch(obj, pattern) { 27 for (const [key, matcher] of Object.entries(pattern)) { 28 if (typeof matcher === 'function') { 29 assert.ok(matcher(obj[key]), `${key} failed validation`); 30 } else if (matcher instanceof RegExp) { 31 assert.match(obj[key], matcher); 32 } else { 33 assert.strictEqual(obj[key], matcher); 34 } 35 } 36} 37 38assertMatch(user, { 39 id: 1, 40 name: 'John', 41 email: /^.+@.+\..+$/, 42 createdAt: d => d instanceof Date 43});

Fail Assertions#

1import { strict as assert } from 'assert'; 2 3// Always fails 4// assert.fail(); 5// assert.fail('Custom error message'); 6// assert.fail(new Error('Custom error')); 7 8// Conditional fail 9function processValue(value) { 10 if (typeof value !== 'number') { 11 assert.fail('Expected number'); 12 } 13 return value * 2; 14} 15 16// Unreachable code 17function handleCase(type) { 18 switch (type) { 19 case 'a': 20 return 1; 21 case 'b': 22 return 2; 23 default: 24 assert.fail(`Unexpected type: ${type}`); 25 } 26}

Testing Examples#

1import { strict as assert } from 'assert'; 2import { describe, it } from 'node:test'; 3 4describe('Calculator', () => { 5 describe('add', () => { 6 it('should add two numbers', () => { 7 assert.strictEqual(add(2, 3), 5); 8 }); 9 10 it('should handle negative numbers', () => { 11 assert.strictEqual(add(-1, 1), 0); 12 }); 13 14 it('should throw for non-numbers', () => { 15 assert.throws( 16 () => add('a', 'b'), 17 TypeError 18 ); 19 }); 20 }); 21}); 22 23// Async testing 24describe('UserService', () => { 25 it('should fetch user by id', async () => { 26 const user = await userService.getById(1); 27 28 assert.deepStrictEqual(user, { 29 id: 1, 30 name: 'John', 31 email: 'john@example.com' 32 }); 33 }); 34 35 it('should throw for non-existent user', async () => { 36 await assert.rejects( 37 userService.getById(999), 38 { name: 'NotFoundError' } 39 ); 40 }); 41});

Custom Assertions#

1import { strict as assert } from 'assert'; 2 3// Create custom assertion functions 4function assertIsPositive(value, message) { 5 assert.ok( 6 typeof value === 'number' && value > 0, 7 message || `Expected positive number, got ${value}` 8 ); 9} 10 11function assertArrayIncludes(arr, item, message) { 12 assert.ok( 13 arr.includes(item), 14 message || `Array does not include ${item}` 15 ); 16} 17 18function assertType(value, type, message) { 19 const actualType = typeof value; 20 assert.strictEqual( 21 actualType, 22 type, 23 message || `Expected ${type}, got ${actualType}` 24 ); 25} 26 27function assertInstanceOf(value, constructor, message) { 28 assert.ok( 29 value instanceof constructor, 30 message || `Expected instance of ${constructor.name}` 31 ); 32} 33 34// Usage 35assertIsPositive(5); // OK 36assertArrayIncludes([1, 2, 3], 2); // OK 37assertType('hello', 'string'); // OK 38assertInstanceOf(new Date(), Date); // OK

AssertionError#

1import { strict as assert, AssertionError } from 'assert'; 2 3// Access AssertionError properties 4try { 5 assert.strictEqual(1, 2); 6} catch (err) { 7 if (err instanceof AssertionError) { 8 console.log('Actual:', err.actual); // 1 9 console.log('Expected:', err.expected); // 2 10 console.log('Operator:', err.operator); // 'strictEqual' 11 console.log('Message:', err.message); 12 } 13} 14 15// Create custom AssertionError 16const error = new AssertionError({ 17 message: 'Custom assertion failed', 18 actual: 'foo', 19 expected: 'bar', 20 operator: 'customAssert' 21}); 22 23// Useful for custom assertions 24function assertBetween(value, min, max) { 25 if (value < min || value > max) { 26 throw new AssertionError({ 27 message: `Expected ${value} to be between ${min} and ${max}`, 28 actual: value, 29 expected: `${min} <= value <= ${max}`, 30 operator: 'assertBetween' 31 }); 32 } 33}

Validation Helpers#

1import { strict as assert } from 'assert'; 2 3// Validation using assertions 4function validateUser(user) { 5 assert.ok(user, 'User is required'); 6 assert.ok(typeof user.name === 'string', 'Name must be a string'); 7 assert.ok(user.name.length >= 2, 'Name must be at least 2 characters'); 8 assert.match(user.email, /^\S+@\S+\.\S+$/, 'Invalid email format'); 9 assert.ok(Number.isInteger(user.age), 'Age must be an integer'); 10 assert.ok(user.age >= 0 && user.age <= 150, 'Age must be between 0 and 150'); 11 12 return true; 13} 14 15// Schema validation 16function assertSchema(obj, schema) { 17 for (const [key, validator] of Object.entries(schema)) { 18 const value = obj[key]; 19 20 if (typeof validator === 'string') { 21 assert.strictEqual(typeof value, validator, `${key} must be ${validator}`); 22 } else if (typeof validator === 'function') { 23 assert.ok(validator(value), `${key} failed validation`); 24 } else if (validator instanceof RegExp) { 25 assert.match(String(value), validator, `${key} format invalid`); 26 } 27 } 28} 29 30assertSchema(user, { 31 name: 'string', 32 age: 'number', 33 email: /^\S+@\S+\.\S+$/, 34 active: v => typeof v === 'boolean' 35});

Best Practices#

Choosing Assertions: ✓ Use strict mode always ✓ Use deepStrictEqual for objects ✓ Use strictEqual for primitives ✓ Use throws/rejects for errors Messages: ✓ Provide descriptive messages ✓ Include actual values in messages ✓ Make failures easy to debug ✓ Use template literals Testing: ✓ One assertion per behavior ✓ Test edge cases ✓ Test error conditions ✓ Use async assertions for promises Avoid: ✗ Legacy loose equality ✗ Catching AssertionErrors silently ✗ Too many assertions per test ✗ Using assert for user input validation

Conclusion#

The Node.js assert module provides essential functions for testing and invariant checking. Always use strict mode (assert/strict) for reliable equality checks. Use throws and rejects for error testing, match for regex validation, and deepStrictEqual for object comparison. While assert is great for tests and development checks, consider proper validation libraries for user input validation in production code.

Share this article

Help spread the word about Bootspring