@artstesh/postboy-testing Reference
Reference page for @artstesh/postboy-testing (applies to v3.4.x; peers @artstesh/postboy ^3.4.1 — the 3.4.x and 3.5.x lines are supported). For the usage guide see Testing and Testing quick start.
BDD-style (Given / When / Then) toolkit: a recording mock over the real bus, fluent assertions, async waiters, and stubs. Named exports only, no default export. Exported from the package root: PostboyServiceMock, HistoryCollection, PostboyTestingSettings, MessageHistory, PostboyWorld, PostboyMessageStreamService, PostboyWorldVerifier, PostboyWaiterService, WaitOptions, PostboyGivenService, PostboyThenService.
PostboyWorld
class PostboyWorld {
constructor(settings: PostboyTestingSettings = { strict: false });
get postboy(): PostboyServiceMock; // inject into the SUT
get history(): MessageHistory;
get given(): PostboyGivenService;
get then(): PostboyThenService;
get waiter(): PostboyWaiterService;
get mocks(): PostboyMessageStreamService;
get registry(): PostboyAbstractRegistrator; // namespaced registrator used internally
dispose(): void;
}
dispose() resets the history, unsubscribes the mocks, tears down the internal mock-namespace…/waiter-namespace… registrators, and disposes the bus — call it in afterEach; the world is not reusable afterwards. The constructor takes only PostboyTestingSettings — the world builds its own history and mock.
interface PostboyTestingSettings { strict: boolean }
strict: false (default): the mock auto-registers unseen message IDs with a dummy Subject and unseen executors with a null-returning function. strict: true: real-bus behavior — unregistered types throw; register them first (e.g. world.registry.recordSubject(Type)). See Testing world.
PostboyServiceMock
class PostboyServiceMock extends PostboyService {
constructor(history: MessageHistory, settings: PostboyTestingSettings = { strict: false });
fire<T>(message: T): void; // records, then real fire
exec<E extends PostboyExecutor<T>, T>(executor: E): T; // records executor, then real exec
sub<T>(type: MessageType<T>): Observable<T>; // counts the subscription
once<T>(type: MessageType<T>): Observable<T>; // counted as well
fireCallback<T>(message: PostboyCallbackMessage<T>, action?: (e: T) => void): Observable<T>; // records + intercepts finish()
isRegistered<T>(type: MessageType<T>): boolean; // whether the id is taken; re-registering would replace the subject
}
Extends the real PostboyService — the whole bus works; recording is layered on top. Prefer building it via PostboyWorld (which supplies the shared history) over constructing it manually.
PostboyGivenService — stubbing
All methods chainable (return this). See Testing world:
callback<T extends PostboyCallbackMessage<R>>(type: MessageType<T>, result: R): PostboyGivenService; // finish(result) on every fire
executor<R, T extends PostboyExecutor<R>>(type: MessageType<T>, result: R): PostboyGivenService; // executor returning `result`
event<T extends PostboyGenericMessage>(message: T): PostboyGivenService; // replay subject for the type AND immediate fire
PostboyThenService — assertions
fired<T extends PostboyMessage>(type: MessageType<T>): PostboyFiredThen<T>;
notFired<T extends PostboyMessage>(type: MessageType<T>): PostboyThenService; // throws if fired
subscribed<T extends PostboyMessage>(type: MessageType<T>): PostboySubscribedThen<T>;
Builders (throw plain Error on failure; and() returns to the then service):
// PostboyFiredThen<T>
.once(): PostboyFiredThen<T>; // exactly 1 fire
.times(count: number): PostboyFiredThen<T>; // exact count
.atLeast(count: number): PostboyFiredThen<T>;
.with(predicate: (m: T) => boolean): PostboyFiredThen<T>; // any recorded message matches
.last(predicate: (m: T) => boolean): PostboyFiredThen<T>; // most recent matches
.first(predicate: (m: T) => boolean): PostboyFiredThen<T>; // earliest matches
get value(): T; // last fired instance; throws if none
and(): PostboyThenService;
// PostboySubscribedThen<T>: .once(); .times(n); .atLeast(n); .and();
See Testing assertions.
PostboyWaiterService — async waits
Default timeout 1000 ms; rejections are descriptive Errors. See Testing async:
waitFor<T>(type: MessageType<T>, options?: WaitOptions<T>): Promise<T>;
waitForMany<T>(type: MessageType<T>, count: number, options?: WaitManyOptions<T>): Promise<T[]>;
waitForAny<T>(types: MessageType<any>[], options?: WaitOptions<any>): Promise<T>;
waitForCallbackResult<T>(type: MessageType<T>, options?: WaitOptions<T>): Promise<R>;
waitForNone<T>(type: MessageType<T>, options?: WaitSilenceOptions<T>): Promise<void>;
delay(ms: number): Promise<void>; dispose(): void;
interface WaitOptions<T> { timeout?: number; where?: (message: T) => boolean; includeHistory?: boolean }
interface WaitManyOptions<T> extends WaitOptions<T> { exact?: boolean } // not exported from root
interface WaitSilenceOptions<T> { timeout?; where?; includeHistory?; timeoutMessage?: string } // not exported from root
Method | Semantics |
|---|
waitFor
| Resolves with the first future message matching where; includeHistory: true first scans recorded messages and resolves synchronously on a hit |
waitForMany
| Collects until count matches; without exact resolves early on the Nth match, with exact: true waits the full timeout and resolves only for exactly N (rejecting on under- and over-count) |
waitForAny
| Races several types; resolves with the first matching message of any of them |
waitForCallbackResult
| Resolves with the result of the next finish(...) of the callback type; includeHistory: true first scans recorded results, same opt-in as waitFor |
waitForNone
| Resolves after timeout ms of silence; rejects immediately on a matching fire (or a history hit with includeHistory); timeoutMessage overrides the rejection text |
delay
| Simple promise-based delay for orchestrating test steps |
MessageHistory & HistoryCollection
class MessageHistory {
messages<T extends PostboyMessage>(type: MessageType<T>): HistoryCollection<T>;
callbackResults<T extends PostboyCallbackMessage<R>>(type: MessageType<T>): HistoryCollection<{ message: T; result: R }>;
callbackResult$<T extends PostboyCallbackMessage<R>>(type: MessageType<T>): Observable<{ message: T; result: R }>;
subs<T extends PostboyMessage>(type: MessageType<T>): number; reset(): void; // subscription count; clear all
}
class HistoryCollection<T> {
add(item: T): void;
hasItem(item: T): boolean;
has(predicate: (i: T) => boolean): boolean;
get last(): T | null; get first(): T | null; get all(): T[]; get length(): number;
clear(): void;
}
callbackResult$ is the hot stream of finish() results that waitForCallbackResult consumes. See Testing assertions.
PostboyWorldVerifier — boolean checks
class PostboyWorldVerifier {
constructor(history: MessageHistory);
fired<T extends PostboyMessage>(type: MessageType<T>, times: number = 0): boolean;
subscribed<T extends PostboyMessage>(type: MessageType<T>, times: number = 0): boolean;
}
Non-throwing counterparts of the then assertions for conditional test logic: true if fired/subscribed at all; exact count when times > 0. The verifier is a separate export — the world has no verifier getter; construct it over the world's history: new PostboyWorldVerifier(world.history).
PostboyMessageStreamService — low-level mocks
class PostboyMessageStreamService {
constructor(postboy: PostboyServiceMock, registry: PostboyAbstractRegistrator);
mockEvent<T extends PostboyMessage>(message: T): void; // recordReplay + immediate fire
mockCallback<R, T extends PostboyCallbackMessage<R>>(type: MessageType<T>, action: (m: T) => R): void;
mockExecute<R, T extends PostboyExecutor<R>>(type: MessageType<T>, action: (m: T) => R): void;
dispose(): void;
}
What given delegates to. Reach for it directly only when a constant result is not enough and you need a custom action function. See Testing recipes.
Not exported from the package root
PostboyFiredThen, PostboySubscribedThen, CallbackResultHistoryItem, PostboyCallbackResult, WaitManyOptions, WaitSilenceOptions, PostboyMessageStoreMock, PostboyMiddlewareServiceMock, PostboyNamespaceStoreMock — reachable only as inferred return/parameter types; do not import them.
07 September 2026