Skip to content
UniKVS
Esc
↑↓navigate↵open⌘Jpreview
On this page

@unikvs/testing

Shared helpers for UniKvs package unit tests: abort checks, bytes, test contexts, deterministic randomness, and stream operations.

Overview

@unikvs/testing Shared provides shared helpers for UniKvs package unit tests. It is a runner-independent set of pure functions: abort-signal checks, thrown-exception capture, byte building and comparison, contexts usable as-is in storage/middleware unit tests, reproducible randomness from fixed seeds, stream driving and verification, and Node.js integration-test support.

Coverage:

Area Contents Details
Abort and exception capture Aborted signals; captured exceptions and rejections Details
Bytes Concatenation, comparison, deterministic generation, splitting Details
Test contexts TestContext and per-operation factories Details
Deterministic randomness Fixed-seed PRNG and repeated runs Details
Streams Feeding, reading, and driving transform streams Details
Node.js support (@unikvs/testing/node) Free-port acquisition, waiting, temp-file listing Details

Installation

npm install @unikvs/testing
pnpm add @unikvs/testing
yarn add @unikvs/testing
bun add @unikvs/testing
nub add @unikvs/testing
aube add @unikvs/testing

ESM only (sideEffects: false). The only runtime dependency is @unikvs/core; the ./node subpath is Node.js-only (uses node:fs/promises and node:net).

Usage

Capture a thrown error first, then verify its type and meta:

import { KeyNotFoundError } from "@unikvs/core";
import { captureRejection, captureThrown } from "@unikvs/testing";

const rejection = await captureRejection(promise);
console.log(rejection instanceof KeyNotFoundError); // true

const thrown = captureThrown(() => fn());
console.log(thrown instanceof Error); // true

In storage/middleware unit tests, build contexts with the per-operation factories:

import { createNext, createSetContext } from "@unikvs/testing";

const ctx = createSetContext("key", { source: data });
await middleware.set?.(ctx, data, createNext());

In randomness-dependent tests, build reproducible inputs from a fixed seed:

import { forCases } from "@unikvs/testing";

forCases(1234, 100, (random, index) => {
  const key = random.string(16);
  // same seed and count always produce the same input
});

In stream tests, combine chunk feeding with reading:

import { collectBytes, sourceFrom } from "@unikvs/testing";

const bytes = await collectBytes(sourceFrom(chunks));

Aborts and exception capture

For entry-point abort checks and verifying thrown error types/meta.abortedSignal always returns an aborted signal;captureRejection and captureThrown return the thrown value as-is, so verify with instanceof and meta. Both throw a bare Error when nothing is thrown.See Failures.

import { abortedSignal } from "@unikvs/testing";

const signal = abortedSignal(new Error("stop"));
console.log(signal.aborted); // true

Building and comparing bytes

For building and comparing expected values. concatBytes and its alias concatChunks join chunk sequences into one byte sequence; bytesEqual compares length and each byte. createAllByteValues generates deterministic bytes cycling 0-255; createPseudoRandomBytes generates deterministic LCG pseudorandom bytes. toHex and toText build expectations without going through implementation-side conversion. splitIntoChunks and splitBySizes make chunk sequences split at arbitrary boundaries, for verifying boundary-independent processing.

import { bytesEqual, concatBytes, createAllByteValues, splitIntoChunks } from "@unikvs/testing";

const bytes = createAllByteValues(600);
const chunks = splitIntoChunks(bytes, 256); // 3-way split: 256, 256, 88
console.log(bytesEqual(concatBytes(chunks), bytes)); // true

Test contexts

TestContext is a concrete test-only IPipelineContext. It holds signal, action, key, destinationKey, repair, source, and destination, with variables handled via get, set, and setReadonly. Structurally assignable to each context type, it works as-is in storage/middleware unit tests. Pass initial values via TestContextInit. TestContextValues is the values form (a Record or an iterable of entries).

Per-operation factories:

Factory action Purpose Details
createNext — Middleware next Details
createOpenContext "open" Opening Details
createCloseContext "close" Closing Details
createSetContext "set" Writing Details
createGetContext "get" Reading Details
createStreamContext "stream" Stream reading Details
createHasContext "has" Presence check Details
createDeleteContext "delete" Deleting Details
createCopyContext "copy" Copying Details
createMoveContext "move" Moving Details
createClearContext "clear" Clearing all Details
createRepairContext "get" Reading with repair Details
createMapKeyContext "set" Key mapping Details
createEncodeContext "set" Encoding Details
createDecodeContext "get" Decoding Details
createEncodeStreamContext "set" Stream encoding Details
createDecodeStreamContext "stream" Stream decoding Details
import { createGetContext, createRepairContext } from "@unikvs/testing";

const ctx = createGetContext("key", { values: { "test:count": 1 } });
console.log(ctx.get("test:count")); // 1

const repair = createRepairContext("key", ["Memory"]);
console.log(repair.repair); // { enabled: true, targets: ["Memory"] }

Deterministic randomness

Random is the operation set for building reproducible inputs from fixed seeds. createRandom builds a mulberry32 generator returning the same values for the same seed and call order. forCases and forCasesAsync run the body a set number of times with seeds derived from the seed and case index. Failed cases include their seed and index in the message and keep the original exception in cause.

import { createRandom, forCasesAsync } from "@unikvs/testing";

const random = createRandom(42);
console.log(random.string(8)); // always the same string

await forCasesAsync(42, 50, async (random, index) => {
  // async cases are also reported with their index
});

Out-of-range arguments (non-integer or inverted ranges for int, negative maxima for uint, picking from empty arrays or alphabets, etc.) throw RangeError.

Driving and verifying streams

For feeding streams and reading from them.sourceFrom and its alias sourceFromValues build a ReadableStream that emits chunks in order and closes.readAll reads to the end and returns output chunks;collectBytes returns all chunks joined as bytes.collectChunks and its alias pipeChunks return the output of feeding chunk sequences through a transform stream.pumpThrough and runTransform write and read concurrently, avoiding backpressure deadlocks while surfacing write/read/close failures.

import { collectChunks, sourceFrom } from "@unikvs/testing";

const output = await collectChunks(new TransformStream(), [1, 2, 3]);
console.log(output); // [1, 2, 3]

Node.js support

@unikvs/testing/node subpath serves Node.js integration tests.acquireFreePort acquires one free port from the OS;waitForPort waits until the port accepts connections.listTemporaryFiles returns names of leftover temp files (*.tmp) directly under the root, for verifying swap-on-close cleanup.

import { acquireFreePort, waitForPort } from "@unikvs/testing/node";

const port = await acquireFreePort();
await waitForPort(port); // waits until connectable

Failures

The test helpers themselves have no error classes. Failure handling:

API reference

Per-symbol reference.

abortedSignal()

Always returns an aborted signal. For entry-point abort checks. Passing reason sets the abort reason.

import { abortedSignal } from "@unikvs/testing";

const signal = abortedSignal(new Error("stop"));
console.log(signal.aborted); // true

captureRejection()

Returns the rejection reason of async processing. Used to verify the type and meta of the actually thrown error. Throws a bare Error when the given Promise resolves.

import { captureRejection } from "@unikvs/testing";

const reason = await captureRejection(promise);

captureThrown()

Returns the exception from sync processing. Used to verify the type and meta of the actually thrown error. Throws a bare Error when nothing is thrown.

import { captureThrown } from "@unikvs/testing";

const error = captureThrown(() => fn());

concatBytes()

Joins chunk sequences into one byte sequence. Used to compare split stream results against a single byte sequence.

import { concatBytes } from "@unikvs/testing";

const bytes = concatBytes([new Uint8Array([1]), new Uint8Array([2, 3])]);

concatChunks()

concatBytes Alias used when chunk-joining runs in a stream context.

bytesEqual()

Compares two byte sequences for equal length and bytes. Avoids the cost of comparing multi-MiB data with toStrictEqual.

import { bytesEqual } from "@unikvs/testing";

console.log(bytesEqual(left, right)); // true or false

createAllByteValues()

Generates deterministic bytes cycling 0-255. For round-trip verification of data containing every byte value. Builds a size-byte array with index % 256 at each position.

import { createAllByteValues } from "@unikvs/testing";

const bytes = createAllByteValues(600); // 0..255, 0..255, 0..87

createPseudoRandomBytes()

Generates deterministic LCG pseudorandom bytes. For preparing unbiased arbitrary binary at scale. Omitting seed uses 0x12345678.

import { createPseudoRandomBytes } from "@unikvs/testing";

const bytes = createPseudoRandomBytes(1024, 42);

toHex()

Converts bytes to a lowercase hex string. Builds expectations without going through implementation-side conversion.

toText()

Converts byte chunk sequences to a UTF-8 string. Compares encode results as strings.

splitIntoChunks()

Splits bytes into chunks of at most the given size. Verifies processing works however boundaries fall. Returned chunks are range views of the original bytes.

import { splitIntoChunks } from "@unikvs/testing";

const chunks = splitIntoChunks(bytes, 256);

splitBySizes()

Cuts bytes into the given lengths in order. Builds chunk sequences split at arbitrary boundaries. Consumes sizes in order, merging the remainder into one trailing chunk. Returned chunks are range views of the original bytes.

TestContextValues

The variable-map form usable as context initial values. Accepts a readonly Record or an iterable of entries ([key, value]).

import type { TestContextValues } from "@unikvs/testing";

const fromRecord: TestContextValues = { count: 1 };
const fromEntries: TestContextValues = [["count", 1]];

TestContextInit

Constructor arguments for TestContext:

PropType
signal?AbortSignal

Signal aborting the operation. Defaults to a fresh `AbortController` signal.

TypeAbortSignal
action?"open" | "close" | "set" | "get" | "stream" | "has" | "delete" | "clear" | "copy" | "move"

Operation kind. Defaults to `"set"`.

Type"open" | "close" | "set" | "get" | "stream" | "has" | "delete" | "clear" | "copy" | "move"
key?string

Logical target key. Defaults to `""`.

Typestring
destinationKey?string

Copy or move destination key. Defaults to `""`.

Typestring
repair?TestContextRepair

Repair request accompanying reads. Meaningful only in `get` and `stream` contexts.

TypeTestContextRepair
source?unknown

Pre-conversion input value.

Typeunknown
destination?unknown

Post-conversion output value.

Typeunknown
values?TestContextValues

Initial values handled by `get` and `set`.

TypeTestContextValues

TestContext

Test-only concrete IPipelineContext (pipeline contexts): holds signal, action, key, destinationKey, repair, source, and destination, with variables handled via get, set, and setReadonly. repair has the TestContextRepair shape, same as IGetContext repair.

PropType
signalAbortSignal

Signal aborting the operation.

TypeAbortSignal
action"open" | "close" | "set" | "get" | "stream" | "has" | "delete" | "clear" | "copy" | "move"

Operation kind.

Type"open" | "close" | "set" | "get" | "stream" | "has" | "delete" | "clear" | "copy" | "move"
keystring

Logical target key.

Typestring
destinationKeystring

Copy or move destination key.

Typestring
repairTestContextRepair

Repair request accompanying reads.

TypeTestContextRepair
sourceany

Pre-conversion input value.

Typeany
destination?any

Post-conversion output value.

Typeany
getget(name: string): any

Gets a variable value.

Typeget(name: string): any
setset(name: string, value: any): this

Sets a variable value. Returns `this`. Throws `ReadonlyVariableError` on readonly-pinned variables.

Typeset(name: string, value: any): this
setReadonlysetReadonly(name: string, value: any): this

Sets a variable value as readonly. Returns `this`. Throws `ReadonlyVariableError` on readonly-pinned variables.

TypesetReadonly(name: string, value: any): this

createNext()

Creates a no-op function for middleware next. Returns a function returning a resolved Promise.

createOpenContext()

Creates a context for open. Returns TestContext with action "open".

createCloseContext()

Creates a context for close. Returns TestContext with action "close".

createSetContext()

Creates a context for set. Sets the first-argument key and returns it with action "set".

import { createSetContext } from "@unikvs/testing";

const ctx = createSetContext("key", { source: data });

createGetContext()

Creates a context for get. Sets the first-argument key and returns it with action "get".

createStreamContext()

Creates a context for stream. Sets the first-argument key and returns it with action "stream".

createHasContext()

Creates a context for has. Sets the first-argument key and returns it with action "has".

createDeleteContext()

Creates a context for delete. Sets the first-argument key and returns it with action "delete".

createCopyContext()

Creates a context for copy. Sets the first-argument key as the source and the second-argument key as the destination, and returns it with action "copy".

import { createCopyContext } from "@unikvs/testing";

const ctx = createCopyContext("source", "destination");

createMoveContext()

Creates a context for move. Sets the first-argument key as the source and the second-argument key as the destination, and returns it with action "move".

import { createMoveContext } from "@unikvs/testing";

const ctx = createMoveContext("source", "destination");

createClearContext()

Creates a context for clear with action "clear". Omitting the key uses "".

createRepairContext()

Creates a get context with repair. Used to verify repair allow/deny. The second-argument target name list is copied, and repair becomes { enabled: true, targets }.

import { createRepairContext } from "@unikvs/testing";

const ctx = createRepairContext("key", ["Memory"]);

createMapKeyContext()

Creates a context for mapKey. Sets the first-argument key. Key mapping itself uses only key, so action stays at the default "set".

createEncodeContext()

Creates a context for encode. Uses the first argument as the pre-conversion input (source) with action "set".

createDecodeContext()

Creates a context for decode. Uses the first argument as the pre-conversion input (source) with action "get".

createEncodeStreamContext()

Creates a context for encodeStream. Uses the first argument as the pre-conversion input stream (source) with action "set".

createDecodeStreamContext()

Creates a context for decodeStream. Uses the first argument as the pre-conversion input stream (source) with action "stream".

Random

The operation set of a deterministic PRNG. Builds reproducible inputs from fixed seeds in randomness-dependent tests.

PropType
nextnext(): number

Returns a fraction in [0, 1).

Typenext(): number
intint(min: number, max: number): number

Returns an integer from `min` to `max` inclusive. Throws `RangeError` on non-integers or inverted ranges.

Typeint(min: number, max: number): number
uintuint(max?: number): number

Returns an integer from 0 to `max` inclusive. `max` defaults to `0xffffffff`. Throws `RangeError` on non-integers or negative values.

Typeuint(max?: number): number
boolbool(): boolean

Returns a boolean.

Typebool(): boolean
pickpick<T>(items: readonly T[]): T

Picks one element from an array. Throws `RangeError` on empty arrays.

Typepick<T>(items: readonly T[]): T
bytesbytes(length: number): Uint8Array<ArrayBuffer>

Returns random bytes of the given length.

Typebytes(length: number): Uint8Array<ArrayBuffer>
stringstring(length: number, alphabet?: string | readonly string[]): string

Returns a string of the given length. Omitting `alphabet` uses lowercase letters and digits. Throws `RangeError` on empty alphabets.

Typestring(length: number, alphabet?: string | readonly string[]): string
arrayarray<T>(length: number, generate: (random: Random, index: number) => T): T[]

Returns an array built by calling `generate` `length` times.

Typearray<T>(length: number, generate: (random: Random, index: number) => T): T[]
shuffleshuffle<T>(items: readonly T[]): T[]

Returns a duplicated, shuffled array. The input is unchanged.

Typeshuffle<T>(items: readonly T[]): T[]

createRandom()

mulberry32 Creates a deterministic PRNG. Returns the same values for the same seed and call order. Makes failures reproducible.

import { createRandom } from "@unikvs/testing";

const random = createRandom(42);
console.log(random.string(8)); // always the same string

forCases()

Runs the test body count times with randomness derived from the same seed. Failed cases include their seed and index in the message and keep the original exception in cause.

import { forCases } from "@unikvs/testing";

forCases(1234, 100, (random, index) => {
  const key = random.string(16);
  // ...
});

forCasesAsync()

forCases Async variant used to report async-case failures with seed and index.

import { forCasesAsync } from "@unikvs/testing";

await forCasesAsync(1234, 50, async (random, index) => {
  // ...
});

sourceFrom()

Creates a ReadableStream emitting the given chunk sequence in order, then closing. Feeds data into streams in arbitrary splits.

import { sourceFrom } from "@unikvs/testing";

const stream = sourceFrom([chunk1, chunk2]);

sourceFromValues()

sourceFrom Alias used to feed arbitrary value sequences into streams.

readAll()

Reads a stream to the end and returns output chunks. Verifies stream output order and content.

collectBytes()

Reads a stream to the end and returns all chunks joined as bytes. Verifies stream reads match plain reads.

import { collectBytes, sourceFrom } from "@unikvs/testing";

const bytes = await collectBytes(sourceFrom(chunks));

collectChunks()

Feeds the given chunk sequence from upstream through a transform stream and returns output chunks in order. Verifies chunk reference, order, and boundaries are preserved.

import { collectChunks } from "@unikvs/testing";

const output = await collectChunks(new TransformStream(), [1, 2, 3]);

pipeChunks()

collectChunks Alias used to verify streams pass input through unchanged.

pumpThrough()

Writes chunk sequences to a transform stream while reading concurrently. Avoids backpressure deadlocks while surfacing write/read errors. Returns readError and writeError (both undefined on success) and outputChunks.

import { pumpThrough } from "@unikvs/testing";

const { readError, writeError, outputChunks } = await pumpThrough(new TransformStream(), [1, 2, 3]);

runTransform()

Writes chunks to a transform stream in order and reads everything readable, concurrently to avoid backpressure deadlocks.Failures from writing, reading, or closing all return as closeError (undefined on success). Output goes in outputChunks. Byte transform streams only.

acquireFreePort()

Acquires one free port from the OS. Stands up a temporary server on 127.0.0.1 and closes it to fix the number, returning a Promise resolving to it.@unikvs/testing/node subpath.

waitForPort()

Waits until the given port accepts connections. Retries connecting to 127.0.0.1 every 250 ms, rejecting with a bare Error past 10 s by default (configurable via timeoutMs).@unikvs/testing/node subpath.

listTemporaryFiles()

Returns names of leftover temp files (*.tmp) directly under the root. Verifies swap-on-close cleanup. Non-recursive.@unikvs/testing/node subpath.

Was this page helpful?