> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prodbreak.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> From zero to a green integration test in under 10 minutes.

By the end of this page you'll have a real test that points the **Stripe SDK** at a ProdBreak
sandbox, forces a `402 card_declined`, and asserts your code handles it — running locally and in CI.

<Note>
  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](/reference/packs) and the control surface
  doesn't change.
</Note>

<Steps>
  <Step title="Install the CLI and your test plugin">
    The CLI spins up sandboxes; the plugin wires the sandbox into your test framework's lifecycle.

    <Tabs>
      <Tab title="Jest">
        ```bash theme={null}
        npm install --save-dev @prod-break/cli @prod-break/jest
        ```
      </Tab>

      <Tab title="Vitest">
        ```bash theme={null}
        npm install --save-dev @prod-break/cli @prod-break/vitest
        ```
      </Tab>

      <Tab title="pytest">
        ```bash theme={null}
        pip install prod-break          # CLI + SDK
        pip install prod-break-pytest   # the pytest fixture plugin
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="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.

    ```bash theme={null}
    npx prod-break run stripe
    # ▸ sandbox ready   url=http://localhost:8801   key=pbw_3f9a…   pack=stripe@0.1.0
    ```

    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](/guides/parallel-workers)).
  </Step>

  <Step title="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`).

    <Tabs>
      <Tab title="Jest">
        ```ts sample.test.ts theme={null}
        import { useSandbox } from "@prod-break/jest";

        const sandbox = useSandbox(); // installs reset() between tests

        test("a declined card surfaces the vendor error to my code", async () => {
          const sb = sandbox();

          // arm the outcome your code must survive — out-of-band, fires on the next charge
          await sb.faults.arm("charges", "create", {
            status: 402,
            code: "card_declined",
            count: 1,
          });

          const stripe = sb.client(); // real Stripe SDK, base URL already repointed
          await expect(
            stripe.charges.create({ amount: 2000, currency: "usd", source: "tok_visa" }),
          ).rejects.toMatchObject({ code: "card_declined", statusCode: 402 });
        });
        ```
      </Tab>

      <Tab title="Vitest">
        ```ts sample.test.ts theme={null}
        import { test } from "@prod-break/vitest"; // injects a per-test `sandbox`

        test("a declined card surfaces the vendor error to my code", async ({ sandbox }) => {
          // arm the outcome your code must survive — out-of-band, fires on the next charge
          await sandbox.faults.arm("charges", "create", {
            status: 402,
            code: "card_declined",
            count: 1,
          });

          const stripe = sandbox.client(); // real Stripe SDK, base URL already repointed
          await expect(
            stripe.charges.create({ amount: 2000, currency: "usd", source: "tok_visa" }),
          ).rejects.toMatchObject({ code: "card_declined", statusCode: 402 });
        });
        ```
      </Tab>

      <Tab title="pytest">
        ```python test_sample.py theme={null}
        import pytest

        def test_declined_card_surfaces_vendor_error(stripe_sandbox):
            # arm the outcome your code must survive — out-of-band, fires on the next charge
            stripe_sandbox.faults.arm("charges", "create",
                                      status=402, code="card_declined", count=1)

            stripe = stripe_sandbox.client()  # real Stripe SDK, base URL already repointed
            with pytest.raises(stripe.error.CardError):
                stripe.Charge.create(amount=2000, currency="usd", source="tok_visa")
            # stripe_sandbox resets the world automatically at test teardown
        ```
      </Tab>
    </Tabs>

    <Note>
      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](/interception#why-armed-faults-are-the-lever).
    </Note>
  </Step>

  <Step title="Run it">
    <Tabs>
      <Tab title="Jest">
        ```bash theme={null}
        npx jest
        ```
      </Tab>

      <Tab title="Vitest">
        ```bash theme={null}
        npx vitest run
        ```
      </Tab>

      <Tab title="pytest">
        ```bash theme={null}
        pytest
        ```
      </Tab>
    </Tabs>

    Green. You just tested an error path that's a nuisance to trigger against the real Stripe — with no
    mocks to maintain and your app's real SDK exercising the real wire format.
  </Step>
</Steps>

## 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 →](/concepts/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 →](/guides/force-an-error))
* **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 →](/interception))

## Next steps

<CardGroup cols={2}>
  <Card title="Seed history" icon="database" href="/guides/seed-history">
    Start a test from the exact state you need.
  </Card>

  <Card title="Test a webhook" icon="webhook" href="/guides/test-a-webhook">
    Receive a real signed webhook and assert on it.
  </Card>

  <Card title="Force a failed branch" icon="code-branch" href="/concepts/branching">
    Drive a workflow down its failure arm on demand.
  </Card>

  <Card title="Make CI deterministic" icon="timer" href="/guides/deterministic-ci">
    Collapse durations so cascades fire inline, no flake.
  </Card>
</CardGroup>
