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.
onPass: called with no arguments on each passing assertion. Default is a no-op.fino:testuses this to count passing assertions.onFail: called with anAssertionErroron each failing assertion. Default throws the error immediately.fino:testoverrides this to push errors into an array for laterAggregateErrorreporting.
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:
- If
checkis an Error constructor, the value must be an instance of it. - If
checkis a function(err) => boolean, it must return true. - If
checkis a RegExp, it is tested againsterr.message. - If
checkis omitted, any throw/rejection passes.
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 () => {});