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

  1. Idempotency. Send the same request twice and assert one message exists.
  2. Out-of-order status callbacks. Deliver a sent after a delivered and assert nothing moved backwards.
  3. Duplicate webhook delivery. Post the same event twice and assert one effect.
  4. A closed window. Assert the composer refuses before the API does.
  5. 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.

Start a trial See pricing