Blog

Why Chaos Injection Uses a CSPRNG (and Rolls Fresh Every Request)

September 5, 2026Xerat022 min read
Share

Real APIs fail sometimes — a flaky upstream, a timeout, a 500 under load. If your client code has never seen that happen, you don't actually know how it behaves when it does. Chaos injection makes MockBase fail on purpose, at a rate you choose, so you can find out before production does.

The mechanism is one line

Each route has a configurable error rate. On every request to that route, MockBase decides independently whether this particular call fails:

secrets.randbelow(10_000) < int(error_rate * 10_000)

error_rate is a float between 0 and 1. Scaling it to an integer out of 10,000 rather than checking random() < error_rate directly gives the roll two extra decimal digits of precision — you can dial in 0.1% as accurately as 10%, not just whole percentages.

The secrets module — not random — is the meaningful choice here. random.random() is a Mersenne Twister: fast, statistically fine for simulations, but seeded and reproducible if you know the state. secrets.randbelow() is cryptographically secure, which matters because chaos injection is a security-adjacent testing feature — people use it to test retry logic, circuit breakers, and failover behavior, and a predictable "random" failure pattern would make those tests less honest than they look.

Independent per request, not a schedule

There's no state carried between requests — no "fail every 10th call," no counter. Each request gets its own fresh roll against the configured rate. Set it to 20% and roughly 1 in 5 calls fails, but which ones is genuinely unpredictable, including runs where three failures land in a row or thirty successes pass before one failure shows up. That's closer to how real intermittent failures actually distribute than a fixed pattern would be — and a fixed pattern is exactly the kind of thing a client could accidentally learn to work around without truly handling the failure case.

What a failed request actually returns

When the roll triggers, MockBase skips your configured response entirely and returns a fixed error shape instead — a configurable status code (so you can test how your client handles a 500 differently from a 503) with a body of {"error": "chaos_injection", "detail": "Injected failure for chaos testing."}. It's unambiguous on purpose: you should never wonder whether a failure during testing was chaos injection or an actual bug in your mock configuration.

Picking a rate

10-20% is usually enough to prove your retry and error-handling paths actually run without making every test run painfully flaky. Save 100% for a deliberate "this endpoint is down" test — verifying a hard dependency failure, not just an intermittent one.

X

Xerat02

Building MockBase.