Get Started: Mock Your Tests in 2 Minutes
This is a hands-on walkthrough of keploy mock 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:
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 testwork 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):
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:
"""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:
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:
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:
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:
# 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:
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:
# 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:
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}
--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}
echo $? # -> 1
macOS
Run your tests through a container and point Keploy at that command:
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.