Async Testing with `waiter`
When your code emits messages inside a subscription, a timeout, or as a reaction to another async process, you can’t just check the history synchronously — the message hasn’t happened yet.
world.waiter solves this: it gives you promise‑based methods that subscribe to the future message stream and resolve when the expected message arrives (or reject if a timeout is reached).
Access it from the world:
Why waiter?
Think of waiter as an async assertion.
Instead of writing flaky setTimeout calls and hoping the event arrives in time, you describe exactly what you’re waiting for.
A typical async test pattern:
waiter takes care of subscribing, filtering, and timing out — you stay focused on your test logic.
waiter.waitFor(type, options?) – wait for a single message
Waits for one message of the given type to be emitted on the mock bus after the call to waitFor.
Options: WaitOptions<T>
timeout– how long to wait (ms). If omitted, the default is 1000 ms. Increase it for slower async workflows.where– a predicate that filters the messages. The promise resolves only when a message passes the filter.includeHistory– by default,waitForignores messages that were already fired. Set this totrueif the message might have been sent before you calledwaitFor.
Examples waitFor
Ignore history (default)
Check history (message already sent)
With a predicate
If no matching message arrives within the timeout, the promise rejects with a descriptive error.
waiter.waitForMany(type, count, options?) – wait for several messages
Waits for at least count messages of the same type.
Options: WaitManyOptions<T>
Extends WaitOptions<T> with one extra field:
exact: false(default) – resolves as soon as at leastcountmessages are collected.exact: true– waits for exactlycountmessages. If more or fewer arrive by the timeout, the promise rejects.
Examples
At least 2 messages
Exactly 2 messages, no more nor fewer
where and includeHistory work the same as in waitFor.
waiter.waitForCallbackResult(type, options?) – wait for a callback outcome
Used with PostboyCallbackMessage<T>.
It subscribes to the callback, intercepts the finish(result) call, and returns the result as a promise.
Under the hood, it does the same as waitFor, but instead of returning the message itself, it returns the value passed to message.finish(...).
Typical usage
where filters the callback message before it is answered.
waiter.waitForNone(type, options?) – verify silence
Asserts that no message of the given type is fired during the specified timeout.
If a matching message does appear (or is already in history when includeHistory: true), the promise rejects.
Options: WaitSilenceOptions<T>
timeoutMessage– a custom message for the rejection error.
includeHistory: true– the promise will also reject if the message was already recorded in the history.
This is especially useful for negative testing — “this action must not trigger an error”.
waiter.waitForAny(types[], options?) – any of multiple types
Waits for the first message that matches any of the provided constructors.
Useful when your code can branch into several outcomes and you want to inspect whichever happens first.
With options (same as WaitOptions, without where per individual type — you can filter afterwards):
If you need per‑type filtering, combine with an if or expect after the promise resolves.
waiter.delay(ms) – simple time helper
A utility to pause execution. Use it sparingly, but sometimes you just need to let an async process settle.
Think of it as a “sleep” for your test. Prefer waitFor/waitForNone whenever a specific message is expected, as delay is not a reliable way to test async behavior.
How waiter works – internal flow
The waiter subscribes to the live message stream. If includeHistory is true, it checks the existing history once before subscribing. When a matching message is found (history or live), the promise resolves and the subscription is cleaned up. If the timeout elapses, it rejects.
Important details & caveats
Default timeout is 1000 ms — adjust per test case.
includeHistoryisfalseby default — you must opt into checking the past.waitFor,waitForMany,waitForAnyonly care about future messages unlessincludeHistoryis set.waiterautomatically unsubscribes upon resolution or rejection, so no manual cleanup is needed inside the test.Errors thrown inside
wherepredicates will propagate to the promise.waitForCallbackResultinterceptsfinish()— it only works with messages that actually callfinish(result). If the handler never calls finish, the promise will timeout.waitForNoneis satisfied only when the timeout expires without a match — it’s a patience‑based check, not a proactive assertion.
Cleanup and test hygiene
Because waiter manages its own subscriptions and cleans them up when the promise settles, you don’t need to manually dispose of anything.
However, you must still call world.dispose() in afterEach to reset the mock bus and history for the next test.
Next steps
You now have all the tools to handle async scenarios. Complement this with:
Given: mock setup – prepare initial events and stub callbacks/executors.
Then: assertions – verify what was recorded after the waiter resolves.
Recipes – complete async testing examples with real‑world patterns.