Skip to main content

@siglo/testing Adapter Guide

@siglo/testing provides zero-flake testing utilities, virtual clocks, step spies, mocks, and failure replay tooling for Siglo workflows.

1. Overview & Architecture

Testing distributed workflows is notoriously difficult due to non-deterministic time delays, external network dependencies, and transient race conditions. @siglo/testing solves this with:
  1. WorkflowHarness: Runs workflows in an isolated test sandbox with built-in step spying and assertions.
  2. FakeClock: Fast-forwards timers deterministically without real-time setTimeout delays.
  3. InMemoryStorageFake: Lightweight, synchronous in-memory storage adapter capturing checkpoints and context mutations.
  4. Execution Recording & Replay: Sanitizes production run records and replays them locally to verify deterministic parity and detect discrepancies.

2. Installation & Requirements

Peer dependencies:
  • @siglo/core: ^1.0.0

3. Basic Test with createWorkflowHarness


4. Mocking Steps & Fault Injection

Mock external APIs, database steps, or payment gateways to simulate failure scenarios:

5. Virtual Clocks (FakeClock)

Control virtual timestamps for harness trace records without waiting for real wall-clock delays. Note that FakeClock only advances its own timestamp counter and tick() flushes microtasks via setImmediate; it does not override Node’s global Date.now or setTimeout:

6. Execution Recording & Deterministic Replay

Record workflow executions in production, sanitize PII, and replay them in CI/CD to detect regressions or output discrepancies:

7. API Reference

WorkflowHarness

  • run(input?, options?): Promise<any>
  • mockStep(stepName: string, implementation: Function): this
  • spyStep(stepName: string): StepSpy
  • assertStepExecuted(stepName: string, expectedCount?: number): void
  • assertStepFailed(stepName: string, expectedError?: string | RegExp): void
  • assertStepRetried(stepName: string, expectedAttempts: number): void
  • exportExecutionRecord(options?: { excludeKeys?: string[] }): ExecutionRecord
  • injectEvent(eventName: string, correlationKey: string, payload?: any): Promise<any>
Note on Harness Scope: WorkflowHarness.run() instruments top-level workflow steps. It does not recursively replace nested branches or mapping children, and injectEvent() resumes the workflow definition directly.

InMemoryStorageFake

  • saveCheckpoint(cp): Promise<void>
  • getCheckpoint(stackId): Promise<any>
  • clear(): void

FakeClock

  • now(): number
  • advanceTime(ms: number): number
  • tick(ms?: number): Promise<void>
  • reset(toTime?: number): void