Skip to content

Testing

What It Is

The testing surface provides deterministic replacements for real time: VirtualClockProvider and VirtualScheduler.

Use this page when retry, timeout, lifecycle timing, scheduled work, or deadline behavior must be asserted without waiting for wall-clock delays.

How It Works

Install the virtual providers before each test, drive time manually, drain scheduled tasks, and restore real providers after the test. Runtime code continues to call Clock and Scheduler; the provider swap makes the behavior deterministic.

The testing utilities are for runtime behavior, not graph validation. Pair them with Validator or dispatcher registration tests when document shape also matters.

Diagrams, Examples, and Outputs

Testing utilities are runtime providers. These references show the contracts they implement:

What It Lets You Do

The testing reference lets applications replace real time with deterministic clock and scheduler implementations.

@studnicky/dagonizer/testing

The testing subpath exports two deterministic replacements for the real-time clock and scheduler. Install them before each test; reset them after.

Code Samples

The code below covers virtual clocks, virtual schedulers, deterministic retry timing, scheduler draining, and provider reset patterns.

Import

ts
import { 
VirtualClockProvider
,
VirtualScheduler
} from '@studnicky/dagonizer/testing';

Class: VirtualClockProvider

In-memory monotonic clock. Time advances only when you advance it.

ts
import { 
VirtualClockProvider
} from '@studnicky/dagonizer/testing';
import {
Clock
} from '@studnicky/dagonizer/runtime';

Constructor

ts
new 
VirtualClockProvider
(0n);

initialNs is the starting nanosecond value. Defaults to 0n.

.tickMs(deltaMs)

ts
clock
.
tickMs
(100);

Advance the virtual clock by deltaMs milliseconds.

.tickNs(deltaNs)

ts
clock
.
tickNs
(100_000_000n);

Advance the virtual clock by deltaNs nanoseconds.

The clock only moves forward — tickNs/tickMs are relative advances, there is no method to set an absolute value after construction. To start the clock at a specific nanosecond value, pass it to the constructor:

ts
const 
clock
= new
VirtualClockProvider
(500_000_000n);

Usage

ts
<<< @/../examples/dags/virtual-clock.ts#virtual-time

Class: VirtualScheduler

In-memory min-heap scheduler. No platform timers. Advance time via advance(ms), runUntil(atMs), or runAll().

ts
import { 
VirtualScheduler
} from '@studnicky/dagonizer/testing';
import {
Scheduler
} from '@studnicky/dagonizer/runtime';

Constructor

ts
new 
VirtualScheduler
(0);

initialAtMs is the starting virtual-now value. Defaults to 0.

.advance(deltaMs)

ts
scheduler
.
advance
(500);

Advance virtual time by deltaMs, firing all tasks scheduled in that window in order.

.runUntil(atMs)

ts
scheduler
.
runUntil
(1000);

Advance virtual time to atMs, firing tasks in order.

.runAll()

ts
scheduler
.
runAll
();

Fire all pending one-shot tasks in monotonic order.

.virtualNow

ts
const 
now
: number =
scheduler
.
virtualNow
;

Current virtual time in ms.

.pendingCount

ts
const 
count
: number =
scheduler
.
pendingCount
;

Number of active (non-cancelled) pending tasks.

Usage with RetryPolicy

ts
<<< @/../examples/dags/virtual-clock.ts#virtual-time

SchedulerProviderInterface interface

Both VirtualScheduler and RealTimeScheduler implement SchedulerProviderInterface:

ts
// SchedulerProviderInterface (from @studnicky/dagonizer/runtime):
//   after(delayMs, options?: { signal? }): Promise<void>
//   at(atMs, options?: { signal? }): Promise<void>
//   every(intervalMs, options?: { signal? }): AsyncIterable<void>
//   cancelAll(): void
const 
_scheduler
: SchedulerProviderInterface = {} as SchedulerProviderInterface;

Implement this interface to create a custom test scheduler (e.g. one that records fired tasks for assertions).

Details for Nerds

Virtual providers are process-global while installed. Reset them after each test so one suite does not leak deterministic time into another.

Prefer advancing virtual time to sleeping. Tests that wait on wall-clock time are slower and less reliable than tests that control Clock and drain Scheduler.

Watched over by the Order of Dagon.