Test it in two layers.
Layer 1: call the handler directly. Most tests call the handler function with a message payload, the same way the broker would. These tests cover:
- business logic
- payload validation
- error paths
- idempotency (handling the same message twice gives the same result as once)
Stub the mailer. Use a real or fake database. There is no broker and no waiting, so the tests are fast and deterministic (same result every run).
Layer 2: a few integration tests with a real broker. Start Redis (for BullMQ) or RabbitMQ with Testcontainers. Publish a message through the real producer. Then wait for the effect: poll until the email record exists or the job completes. Use a timeout, not a fixed sleep. These tests prove the wiring works:
- queue names
- serialization (turning the message into bytes and back)
- acks (the consumer confirming it handled the message)
- retry and dead-letter settings
Also test what happens when the handler throws an error. The message must be retried or moved to a dead-letter queue (a separate queue for failed messages). It must not be lost.
- Call the handler directly to test logic and edge cases.
- Add a few integration tests with a real broker to check the wiring.
- Poll for the effect with a timeout, and never use a fixed sleep.
- Test failure handling: retries and dead-lettering.
- Testcontainers for Node.js (https://node.testcontainers.org/)node.testcontainers.org