Testing something that sends real messages to real people
Fake the provider at the HTTP boundary, put an allow-list guard between your code and any real send outside production, and accept that exactly one kind of test has to hit the real API - the one that proves your request shape is still right.
The guard that has to exist
def guard(msisdn: str) -> None:
if settings.environment == "production":
return
if msisdn not in settings.test_numbers:
raise RuntimeError(
f"{settings.environment} tried to message {msisdn}, which is "
"not in TEST_NUMBERS. This is the guard working."
)It should raise loudly rather than skip quietly. A silent skip means a test suite that passes while proving nothing, and the day the guard is wrong you want to know.
This exists because of one specific incident shape: a staging environment restored from a production database backup, and a job that messages every customer. The customer list is real; the environment is not. Without the guard, everybody gets a duplicate order notification from a system that does not exist.
What to fake, and where
- Fake at the HTTP boundary, not by stubbing your own send function. Faking your own code tests the mock.
- Record real responses once and replay them. Handwritten fixtures drift from reality and are wrong in the interesting cases - the errors.
- Fake the failures too. A 429, a closed window, an invalid recipient, a timeout that succeeded anyway. Those paths are the ones that break in production and the ones nobody tests.
The tests that matter most
- Idempotency. Send the same request twice and assert one message exists.
- Out-of-order status callbacks. Deliver a
sentafter adeliveredand assert nothing moved backwards. - Duplicate webhook delivery. Post the same event twice and assert one effect.
- A closed window. Assert the composer refuses before the API does.
- Signature rejection. Post an unsigned webhook and assert a 401.
The one real send
A scheduled check, in production, to a number you own, that asserts the message actually arrives. Everything else can pass while the provider quietly changes a required field. It is also the cheapest possible monitor for the session having ended.
Questions people actually ask
How do I test WhatsApp sending without spamming people?
Fake the provider at the HTTP boundary for almost everything, keep an allow-list of numbers any non-production environment may send to, and reserve real sends for a small number of deliberate checks.
Should staging use a real WhatsApp number?
Yes, a separate one. Sharing production's number means a test run damages a real number's quality rating, and that damage is not reversible on demand.
Njiwa
One inbox for WhatsApp, Facebook and Instagram, and an API that sends over either WhatsApp rail. 7 days free, no card.