Testing
Drive time deterministically with FakeClock so timeouts, backoff and cooldowns are instant and reproducible — and validate a custom store against the shared contract suite.
Resilience is full of time — timeouts, retry backoff, breaker cooldowns. To test it without real waiting, every time-dependent policy accepts an injectable Clock, and the package ships a FakeClock you advance by hand.
The clock seam
interface Clock {
now(): number;
setTimer(ms: number, cb: () => void): () => void; // returns a cancel fn
delay(ms: number, signal?: AbortSignal): Promise<void>;
}timeout, retry, circuitBreaker's store, and InMemoryResilienceStore all take { clock }. Pass a FakeClock and call advance(ms) to fire timers instantly:
import { FakeClock, timeout, TimeoutError } from '@dudousxd/nestjs-resilience';
it('times out without real waiting', async () => {
const clock = new FakeClock();
const policy = timeout(1_000, { clock });
const pending = policy.execute(() => new Promise(() => {})); // never resolves
clock.advance(1_000); // jump past the deadline
await expect(pending).rejects.toBeInstanceOf(TimeoutError);
});The same trick drives a breaker through its cooldown deterministically:
import { FakeClock, InMemoryResilienceStore, circuitBreaker, BrokenCircuitError } from '@dudousxd/nestjs-resilience';
const clock = new FakeClock();
const store = new InMemoryResilienceStore(clock);
const breaker = circuitBreaker({ key: 'svc', store, threshold: 1, cooldownMs: 5_000 });
await expect(breaker.execute(() => Promise.reject(new Error('boom')))).rejects.toThrow(); // opens
await expect(breaker.execute(() => Promise.resolve('ok'))).rejects.toBeInstanceOf(BrokenCircuitError); // open
clock.advance(5_000); // cooldown elapses → half-open
await expect(breaker.execute(() => Promise.resolve('ok'))).resolves.toBe('ok'); // probe closes itFakeClock fires timers in scheduled order as you advance, so retry backoff (100ms → 200ms → 400ms) plays out across advance calls with no setTimeout and no flakiness.
Validating a store
Writing your own ResilienceStore? The package exposes the same contract every built-in adapter passes, from the dedicated testing subpath — kept separate so the production barrel never pulls in your test runner:
import { runResilienceStoreContract } from '@dudousxd/nestjs-resilience/testing';
import { MyResilienceStore } from './my-store';
runResilienceStoreContract('MyResilienceStore', (clock) => new MyResilienceStore({ clock }));runResilienceStoreContract(name, makeStore) registers a describe block that hammers atomicity and the state machine — including the key invariant that N concurrent admit calls in half-open yield exactly one probe. The factory receives a Clock so the suite controls time.
The distributed adapters run this same suite against real engines with testcontainers. If your store passes it, it behaves like the built-ins under concurrency.