402 card_declined, and asserts your code handles it — running locally and in CI.
We use Stripe here because everyone knows its
402. The flow is identical for every pack — swap
stripe for twilio, github, deck, or your own pack and the control surface
doesn’t change.Install the CLI and your test plugin
The CLI spins up sandboxes; the plugin wires the sandbox into your test framework’s lifecycle.
- Jest
- Vitest
- pytest
Start a sandbox
Run one Stripe sandbox locally. It prints a URL and an instance-minted key — the key
authenticates you to this one isolated world.Your test plugin reads
PRODBREAK_URL / PRODBREAK_KEY from the environment, so export what the
CLI prints (or let the plugin spawn the instance for you — see
Parallel workers).Write your first test
The fixture hands you two things:
client() — the real Stripe SDK, already pointed at this
world — and a control handle (seed, faults, world, clock, webhooks, exogenous).- Jest
- Vitest
- pytest
sample.test.ts
You armed the fault out of band rather than passing a magic input. That’s deliberate: it’s
the one control that works even when unmodified app code — not your test — makes the call.
See Connect your app.
What just happened
- One instance = one world. The CLI booted an isolated sandbox and minted its own key. Your test
got that whole world to itself; the plugin
reset()s it between tests so they stay independent. (Worlds →) - You forced a cause, the sandbox produced the effect. You armed a fault; ProdBreak returned Stripe’s real error envelope. You never hand-wrote a response body. (Forced outcomes →)
- Your app didn’t change. Only the base URL moved. The Stripe SDK serialized, signed, and sent the exact request it always does. (Connect your app →)
Next steps
Seed history
Start a test from the exact state you need.
Test a webhook
Receive a real signed webhook and assert on it.
Force a failed branch
Drive a workflow down its failure arm on demand.
Make CI deterministic
Collapse durations so cascades fire inline, no flake.