assert

js/test/assert.ts

fino:assert — assertion library with configurable pass/fail callbacks.

This module provides the assertion primitives used by fino:test. It is also usable as a standalone library for any code that needs structured assertions.

The key design decision is that pass/fail behavior is injectable via constructor callbacks rather than hard-coded. This allows fino:test to use collect-then-throw semantics (all assertions run before any failure is reported) while standalone users get the default throw-immediately behavior.

The Assert class

new Assert({ onPass, onFail }) creates a configurable assertion instance.

Because both callbacks are injectable, a single Assert class handles all use cases without subclassing.

AssertionError

Extends Error with actual, expected, and operator fields, matching the shape of Node.js's assert.AssertionError. The operator field names the assertion that failed (e.g. "equal", "throws").

Deep equality

deepEqual(actual, expected) uses _deepEqual(), which recursively compares own enumerable string and symbol keys. It supports arrays, plain objects, Map, Set, Date, RegExp, typed arrays, and cyclic object graphs. Non-enumerable properties and prototype identity are outside this lightweight assertion layer's comparison contract.

throws / rejects

Both accept an optional check argument:

Default instance and named exports

A module-level _default = new Assert() instance is exported as the default export and also as named free functions (ok, equal, throws, etc.). This lets callers choose between:

import assert from './assert.ts';        // default instance
assert.ok(value);

import { ok, equal } from './assert.ts'; // named free functions
ok(value);
equal(got, expected);
import { Assert, AssertionError } from './assert.ts';

const assert = new Assert({
  onFail(err) { myFailures.push(err); },
});
assert.ok(value, 'must be truthy');
assert.equal(got, expected, 'same value');
assert.deepEqual({ a: 1 }, { a: 1 }, 'same shape');
await assert.rejects(async () => { throw new Error('oops'); }, /oops/);

Interfaces

interface AssertionErrorOptions {

Constructor options for AssertionError.

Properties

message?: string
actual?: unknown
expected?: unknown
operator?: string

interface AssertCallbacks {

Callback hooks used by Assert to report pass and fail events.

Properties

onPass?: () => void
onFail?: (err: AssertionError) => void

Types

type ErrorConstructor = new (...args: any[]) => Error

Matcher accepted by throws() and rejects().

A function receives the thrown value and must return true. A regular expression is tested against the error message. null and undefined accept any thrown or rejected value.

type ErrorCheck = ((e: unknown) => boolean) | ErrorConstructor | RegExp | null

Classes

class AssertionError extends Error {

Error thrown by failed assertions, including actual, expected, and operator metadata.

Assertion methods create this error and either throw it immediately or pass it to a custom onFail callback. The metadata fields are useful for TAP output, custom reporters, and debugging failed test expectations.

import { AssertionError } from 'fino:test/assert';

const err = new AssertionError({
  message: 'expected count',
  actual: 1,
  expected: 2,
  operator: 'equal',
});

Constructors

constructor({ message, actual, expected, operator }: AssertionErrorOptions = {})

Create an assertion error.

Omitted fields stay undefined, and the message defaults to "Assertion failed". The constructor does not inspect or format values; assertion methods prepare human-readable messages before constructing it.

import { AssertionError } from 'fino:test/assert';

throw new AssertionError({ message: 'custom failure', operator: 'fail' });

Getters

get actual()

Value produced by the code under test.

import { AssertionError } from 'fino:test/assert';

const err = new AssertionError({ actual: 1 });
err.actual; // 1
get expected()

Value the assertion expected.

import { AssertionError } from 'fino:test/assert';

const err = new AssertionError({ expected: 2 });
err.expected; // 2
get operator()

Assertion operator that failed, such as equal or throws.

import { AssertionError } from 'fino:test/assert';

const err = new AssertionError({ operator: 'equal' });
err.operator; // 'equal'

class Assert {

Configurable assertion helper. Each method calls onPass on success or onFail(AssertionError) on failure.

Default: no-op. Default: throws the error.

import { Assert } from 'fino:test/assert';

const failures: Error[] = [];
const assert = new Assert({ onFail: (err) => failures.push(err) });
assert.ok(false);
failures.length; // 1

Constructors

constructor({ onPass, onFail }: AssertCallbacks = {})

Create an assertion helper with optional pass/fail callbacks.

The default onFail throws immediately. Test runners can collect errors by providing onFail and count successful assertions with onPass.

import { Assert } from 'fino:test/assert';

let passed = 0;
const assert = new Assert({ onPass: () => passed++ });
assert.equal(1, 1);

Methods

ok(value: unknown, msg?: string): void

Assert that value is truthy.

Fails for JavaScript-falsy values (false, 0, '', null, undefined, and NaN). The optional message prefixes the generated failure text.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.ok('non-empty');
notOk(value: unknown, msg?: string): void

Assert that value is falsy.

Use this for explicit negative conditions. Passing a truthy value fails with operator set to notOk.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.notOk('');
equal(actual: unknown, expected: unknown, msg?: string): void

Assert strict equality using ===.

This does not coerce types and does not perform deep comparison. Use deepEqual() for plain object or array structure checks.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.equal(1 + 1, 2);
notEqual(actual: unknown, expected: unknown, msg?: string): void

Assert strict inequality using !==.

Fails when the two values are strictly equal.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.notEqual('1', 1);
strictEqual(actual: unknown, expected: unknown, msg?: string): void

Assert strict equality using ===.

Node-compatible alias for equal() with an operator name of strictEqual.

notStrictEqual(actual: unknown, expected: unknown, msg?: string): void

Assert strict inequality using !==.

Node-compatible alias for notEqual() with an operator name of notStrictEqual.

deepEqual(actual: unknown, expected: unknown, msg?: string): void

Assert deep equality of plain objects and arrays.

The comparison walks own enumerable string keys and uses strict equality at leaves. It intentionally does not special-case Map, Set, Date, RegExp, symbol keys, or non-enumerable properties.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.deepEqual({ tags: ['a'] }, { tags: ['a'] });
fail(msg?: string): void

Unconditionally fail with a message.

This is useful for unreachable branches or callbacks that should not run. The failure uses operator set to fail.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.fail('unreachable');
match(actual: string, expected: RegExp, msg?: string): void

Assert that a string matches a regular expression.

The comparison uses RegExp.prototype.test() against String(actual).

throws(fn: () => void, check?: ErrorCheck, msg?: string): void

Assert that fn throws synchronously. Optionally validate the thrown value with a check function (err) => boolean or a RegExp tested against err.message.

The function is called immediately and must throw before returning. Use rejects() for promise-returning code.

import { Assert } from 'fino:test/assert';

const assert = new Assert();
assert.throws(() => JSON.parse('{'), /JSON/);
doesNotThrow(fn: () => void, check?: ErrorCheck, msg?: string): void

Assert that fn does not throw synchronously.

The optional check is accepted for API compatibility and is recorded as the expected value when a failure is reported.

async rejects(fn: () => Promise<unknown>, check?: ErrorCheck, msg?: string): Promise<void>

Assert that fn returns a promise that rejects. Optionally validate the rejection value with a check function or RegExp.

Must be awaited: await assert.rejects(async () => { ... }).

import { Assert } from 'fino:test/assert';

const assert = new Assert();
await assert.rejects(async () => {
  throw new Error('network');
}, /network/);
async doesNotReject(fn: () => Promise<unknown>, check?: ErrorCheck, msg?: string): Promise<void>

Assert that fn returns a promise that resolves.

The optional check is accepted for API compatibility and is recorded as the expected value when a failure is reported.

Constants

const ok

Assert that a value is truthy using the default Assert instance.

import { ok } from 'fino:test/assert';

ok(true);

const notOk

Assert that a value is falsy using the default Assert instance.

import { notOk } from 'fino:test/assert';

notOk(false);

const equal

Assert strict equality using the default Assert instance.

import { equal } from 'fino:test/assert';

equal(1 + 1, 2);

const notEqual

Assert strict inequality using the default Assert instance.

import { notEqual } from 'fino:test/assert';

notEqual('1', 1);

const strictEqual

Assert strict equality using the default Assert instance.

import { strictEqual } from 'fino:test/assert';

strictEqual(1 + 1, 2);

const notStrictEqual

Assert strict inequality using the default Assert instance.

import { notStrictEqual } from 'fino:test/assert';

notStrictEqual('1', 1);

const deepEqual

Assert structural equality for plain object and array values.

import { deepEqual } from 'fino:test/assert';

deepEqual({ a: [1] }, { a: [1] });

const fail

Unconditionally fail an assertion.

import { fail } from 'fino:test/assert';

fail('expected branch not reached');

const match

Assert that a string matches a regular expression.

import { match } from 'fino:test/assert';

match('hello', /ell/);

const throws

Assert that a synchronous function throws, optionally matching the error.

import { throws } from 'fino:test/assert';

throws(() => JSON.parse('{'), /JSON/);

const doesNotThrow

Assert that a synchronous function does not throw.

import { doesNotThrow } from 'fino:test/assert';

doesNotThrow(() => JSON.parse('{}'));

const rejects

Assert that an async function rejects, optionally matching the error.

import { rejects } from 'fino:test/assert';

await rejects(async () => { throw new Error('boom'); }, /boom/);

const doesNotReject

Assert that an async function resolves.

import { doesNotReject } from 'fino:test/assert';

await doesNotReject(async () => {});