Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions versioned_docs/version-4.0.0/running-keploy/mock-quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
---
id: mock-quickstart
title: "Get Started: Mock Your Tests in 2 Minutes"
sidebar_label: Mock Quickstart
description: 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.
tags:
- mocks
- quickstart
- pytest
keywords:
- keploy mock quickstart
- mock record
- mock replay
- getting started
- sample app
---

# 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
import http.server, json, socketserver, sys
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."""
import json, urllib.request
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).
Loading
Loading