# Get Started: Mock Your Tests in 2 Minutes

> A copy-paste walkthrough with a sample app — record the dependency calls its tests make, then replay them offline. Shows the real terminal output at each step.

# Get Started: Mock Your Tests in 2 Minutes

This is a hands-on walkthrough of [`keploy mock`](./mock-your-tests.md) with a
small sample app. You'll test an app that calls an external API, record the
API's responses, then run the same tests with the API **switched off**. Every
command shows the **real output** you should see — nothing is faked.

## Prerequisites

- Keploy installed — see [Installation](../server/installation.md):

  ```bash
  keploy --version
  ```

- **Linux** (root, for eBPF) or **Windows amd64** (Administrator). On **macOS**,
  run your tests through a docker command (shown at the end).
- Python 3. (`go test` / `npm test` work identically — only the test command
  changes.)

## Step 1 — the sample app

A tiny **billing app** that depends on an external **users API**. Three files in
one folder:

`users_api.py` — the external dependency (stands in for a real upstream service):

```python

PORT = int(sys.argv[1])
users = {"1": {"id": 1, "name": "Ada Lovelace", "plan": "pro"},
         "2": {"id": 2, "name": "Alan Turing", "plan": "free"}}
plans = {"free": {"price": 0}, "pro": {"price": 20}}

class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith("/plans/"):
            p = plans.get(self.path.rsplit("/", 1)[-1]); body = json.dumps(p or {}).encode()
        else:
            u = users.get(self.path.rsplit("/", 1)[-1]); body = json.dumps(u or {"error": "not found"}).encode()
        self.send_response(200); self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body))); self.end_headers(); self.wfile.write(body)
    def log_message(self, *a): pass

socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("127.0.0.1", PORT), H) as s:
    s.serve_forever()
```

`billing.py` — **your app**: business logic that calls the users API:

```python
"""A tiny billing app that depends on an external users API."""

USERS_API = "http://127.0.0.1:8085"

def _get(path):
    with urllib.request.urlopen(f"{USERS_API}{path}", timeout=5) as r:
        return json.load(r)

def monthly_bill(user_id: int) -> str:
    """Look up a user and format their monthly bill."""
    user = _get(f"/users/{user_id}")
    badge = "PRO" if user["plan"] == "pro" else "FREE"
    price = 20 if user["plan"] == "pro" else 0
    return f'{user["name"]} ({badge}): ${price}/mo'
```

`test_billing.py` — **your tests**, which exercise the app:

```python
from billing import monthly_bill

def test_pro_user_bill():
    assert monthly_bill(1) == "Ada Lovelace (PRO): $20/mo"

def test_free_user_bill():
    assert monthly_bill(2) == "Alan Turing (FREE): $0/mo"
```

Start the dependency in one terminal:

```bash
python3 users_api.py 8085
```

Your tests pass against the live API — that's the state you want to capture.

## Step 2 — record

In another terminal, run your test command under `keploy mock record`:

```bash
keploy mock record -c "python3 -m pytest -q"
```

```
🐰 Keploy: INFO  Recording mocks for your test command  {"mock-set": "billing", "command": "python3 -m pytest -q"}
🐰 Keploy: INFO  Successfully cleared old mocks for refresh.  {"testSet": "billing"}
🐰 Keploy: INFO  Starting Application :  {"executing_cmd": "/usr/bin/sh -c python3 -m pytest -q"}
2 passed in 0.01s
🐰 Keploy: INFO  recorded mocks  {"mocks": 2, "mock-set": "billing"}
🐰 Keploy: INFO  test command finished successfully  {"phase": "record"}
```

Keploy captured the two `/users/…` calls `billing.py` made into
`keploy/billing/mocks.yaml`:

```yaml
# Generated by Keploy
version: api.keploy.io/v1beta1
kind: Http
name: mock-0
spec:
  metadata:
    type: HTTP_CLIENT
  req:
    method: GET
    url: /users/1
    header:
      Host: 127.0.0.1:8085
  resp:
    status_code: 200
    body: '{"id": 1, "name": "Ada Lovelace", "plan": "pro"}'
```

Commit `keploy/` to your repo like a VCR cassette. Re-recording the same set
**overwrites it in place**, so refreshing on a merge to `main` is a clean diff.

## Step 3 — replay with the dependency off

**Stop `users_api.py`** (Ctrl+C in the first terminal). Then run the same tests
under `keploy mock replay`:

```bash
keploy mock replay -c "python3 -m pytest -q"
```

```
🐰 Keploy: INFO  Replaying mocks for your test command  {"mock-set": "billing", "command": "python3 -m pytest -q", "on-miss": "fail"}
🐰 Keploy: INFO  Starting Application :  {"executing_cmd": "/usr/bin/sh -c python3 -m pytest -q"}
2 passed in 0.01s
🐰 Keploy: INFO  mock replay summary  {"loaded": 2, "consumed": 2, "missed": 0}
🐰 Keploy: INFO  test command finished successfully  {"phase": "replay"}
```

Your app's tests pass with **no users API running** — Keploy served every call
from the recorded mocks. That's the whole point: fast, hermetic tests with no
live dependency.

## Step 4 — a new dependency call (refresh)

You add a feature that calls a **new endpoint**. Extend the app and its test:

```python
# billing.py
def plan_price(plan_id: str) -> int:
    return _get(f"/plans/{plan_id}")["price"]   # a new endpoint

# test_billing.py
def test_pro_plan_price():
    from billing import plan_price
    assert plan_price("pro") == 20
```

Start the dependency again and replay with `--on-miss record`. Known calls are
served from mocks; the new `/plans/pro` call goes to the real API **and is
appended** to the set:

```bash
keploy mock replay -c "python3 -m pytest -q" --on-miss record
```

```
🐰 Keploy: INFO  on-miss: served an unrecorded call from the real dependency  {"method": "GET", "url": "/plans/pro", "policy": "record"}
3 passed in 0.01s
🐰 Keploy: INFO  appended new dependency calls to the mock set (--on-miss record)  {"new": 1, "mock-set": "billing"}
```

Stop the dependency and replay normally — all three tests now pass from mocks:

```
3 passed in 0.01s
🐰 Keploy: INFO  mock replay summary  {"loaded": 3, "consumed": 3, "missed": 0}
```

:::note New endpoint vs new path parameter
`--on-miss record` fires only on a call that matches **no** recorded mock.
Keploy's HTTP matcher treats dynamic-looking path segments (numeric IDs, UUIDs)
as wildcards, so `monthly_bill(3)` → `/users/3` would match the recorded
`/users/1` mock and is **not** a miss — only a new endpoint like `/plans/pro`
is. Pass `--disableAutoURLDynamic` for strict URL matching, or re-record.
:::

## Step 5 — it fails the build when your tests fail

Keploy mirrors the runner's exit code, so it gates CI. If a test fails during
replay:

```
1 failed, 2 passed in 0.01s
🐰 Keploy: ERROR error while running the app  {"error": "exit status 1"}
🐰 Keploy: INFO  test command exited non-zero; mirroring its exit code  {"phase": "replay", "exitCode": 1}
```

```bash
echo $?    # -> 1
```

## macOS

Run your tests through a container and point Keploy at that command:

```bash
keploy mock record -c "docker compose run --rm tests"
keploy mock replay -c "docker compose run --rm tests"
```

## Next

- Full reference, `--strict`, and the per-test **scope API** (isolate mocks per
  test from pytest/go/jest): [Mock Your Own Tests](./mock-your-tests.md).
