Testing
@noocodex/dagonizer/testing
The testing subpath exports two deterministic replacements for the real-time clock and scheduler. Install them before each test; reset them after.
Class: VirtualClockProvider
In-memory monotonic clock. Time advances only when you advance it.
import { VirtualClockProvider } from '@noocodex/dagonizer/testing';
import { Clock } from '@noocodex/dagonizer/runtime';Constructor
new VirtualClockProvider(initialNs?: bigint)initialNs is the starting nanosecond value. Defaults to 0n.
.tickMs(deltaMs)
tickMs(deltaMs: number): voidAdvance the virtual clock by deltaMs milliseconds.
.tickNs(deltaNs)
tickNs(deltaNs: bigint): voidAdvance the virtual clock by deltaNs nanoseconds.
.setNs(ns)
setNs(ns: bigint): voidSet the virtual clock to an absolute nanosecond value.
Usage
import { VirtualClockProvider } from '@noocodex/dagonizer/testing';
import { Clock } from '@noocodex/dagonizer/runtime';
import { after, before, describe, it } from 'node:test';
describe('lifecycle timestamps', () => {
const clock = new VirtualClockProvider(0n);
before(() => Clock.configure(clock));
after(() => Clock.reset());
it('records duration correctly', async () => {
const state = new MyState();
state.markRunning();
clock.tickMs(100);
state.markCompleted();
const lc = state.lifecycle;
assert.strictEqual(lc.kind, 'completed');
assert.strictEqual(lc.finishedAt - lc.startedAt, 100);
});
});Class: VirtualScheduler
In-memory min-heap scheduler. No platform timers. Advance time via advance(ms), runUntil(atMs), or runAll().
import { VirtualScheduler } from '@noocodex/dagonizer/testing';
import { Scheduler } from '@noocodex/dagonizer/runtime';Constructor
new VirtualScheduler(initialAtMs?: number)initialAtMs is the starting virtual-now value. Defaults to 0.
.advance(deltaMs)
advance(deltaMs: number): voidAdvance virtual time by deltaMs, firing all tasks scheduled in that window in order.
.runUntil(atMs)
runUntil(atMs: number): voidAdvance virtual time to atMs, firing tasks in order.
.runAll()
runAll(): voidFire all pending one-shot tasks in monotonic order.
.virtualNow
get virtualNow(): numberCurrent virtual time in ms.
.pendingCount
get pendingCount(): numberNumber of active (non-cancelled) pending tasks.
Usage with RetryPolicy
import { VirtualScheduler } from '@noocodex/dagonizer/testing';
import { VirtualClockProvider } from '@noocodex/dagonizer/testing';
import { Scheduler, Clock, RetryPolicy, BackoffStrategy } from '@noocodex/dagonizer/runtime';
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
describe('RetryPolicy', () => {
const clock = new VirtualClockProvider(0n);
const scheduler = new VirtualScheduler(0);
before(() => {
Clock.configure(clock);
Scheduler.configure(scheduler);
});
after(() => {
Clock.reset();
Scheduler.reset();
});
it('retries with exponential backoff', async () => {
let attempts = 0;
const policy = new RetryPolicy({
maxAttempts: 3,
strategy: BackoffStrategy.EXPONENTIAL,
baseDelay: 1000,
jitterFactor: 0,
});
const promise = policy.run(async () => {
attempts++;
if (attempts < 3) throw new Error('transient');
return 'ok';
});
// Step through retry delays
scheduler.advance(1000); // attempt 1 → sleep 1000ms
scheduler.advance(2000); // attempt 2 → sleep 2000ms
const result = await promise;
assert.strictEqual(result, 'ok');
assert.strictEqual(attempts, 3);
});
});SchedulerProvider interface
Both VirtualScheduler and RealTimeScheduler implement SchedulerProvider:
interface SchedulerProvider {
after(delayMs: number, signal?: AbortSignal): Promise<void>;
at(atMs: number, signal?: AbortSignal): Promise<void>;
every(intervalMs: number, signal?: AbortSignal): AsyncIterable<void>;
cancelAll(): void;
}Implement this interface to create a custom test scheduler (e.g. one that records fired tasks for assertions).