We had a handler that issued a branded invoice when a subscription payment came in. It was implemented. It was unit-tested. It had never run in production — because nothing subscribed it to its event. The event fired on every payment and died with no consumer; no invoice was ever issued.
The lesson is uncomfortable and worth internalising: a green unit test on a handler proves the handler works when you call it, not that anything calls it. The wiring — the subscription that connects an event type to a handler — is a separate fact that needs its own verification.
How to verify the wiring, not just the logic
Write a test that drives the real dispatch path: emit the event the way production emits it, then assert the side effect actually happened — the row was written, the email was sent. If the subscription is missing, that test fails even though the handler's own unit test is green. That difference is the entire point: one test exercises the function, the other exercises the connection.
Sync or async — decide on purpose
Once an event has a consumer, how it runs matters:
- Synchronous handlers run inline as the event is emitted. Choose this for a side effect that must happen with the triggering action and cannot tolerate a queue that might not drain — for example, issuing a financial document the moment money is received.
- Asynchronous handlers run later off a queue. Choose this for work that can lag a little — a notification email, a mirror to an external accounting system.
Defaulting everything to async is convenient until the queue stalls and a money-critical document silently never appears. Match the delivery mode to the cost of the side effect not happening.