Status: pre-1.0. Minors may break mocks-file shape — see docs/VERSIONING.md. Pin
$schematohttps://schemas.mockstar.dev/v0.<N>/mock.jsonfor stability.
A Bun-based mock server with static + dynamic mocking, JSON config, named JS handlers, pass-through routing, multi-tenancy, and test-data utilities.
Recorded with asciinema — raw cast at docs/media/demo.cast. Regenerate with make record-demo (requires asciinema, jq, npx, Docker).
Mockstar ships a library embed (import { launch } from '@dhanesh/mockstar') supported
across Jest 30, Jest 29, Vitest, and bun test. See docs/SDET.md
- the
examples/sdet-*directories.
Read CONTRIBUTING.md. Maintainers: docs/TEAM-WORKFLOW.md.
Mockstar targets three personas equally:
- SDETs — library-embed in test suites, ephemeral instances per CI run, deterministic mode
- Developers —
bunx @dhanesh/mockstar ./mockswith file-watch hot reload - DevOps — Docker image in shared staging with per-tenant ConfigMap mounts
Built on Hono on Bun. Targets p99 < 5ms for static mock responses. See DECISIONS.md for the constraint-first design record.
bun install
bun run src/cli.ts ./examples/mocks
# http://localhost:3000A persona-specific deployment walkthrough lives in docs/DEPLOYMENT.md.
mocks/
default/ # one directory per tenant
users.json # a mock config file
orders.json
handlers/ # named JS function handlers
echo.ts
See docs/CONFIG.md for the full schema and docs/HANDLERS.md for dynamic handlers.
Any string value inside response.body or response.headers can contain {{ … }} placeholders. Whole-string placeholders in a JSON body preserve their source type — a number stays a number, an object stays an object.
| Token | Value |
|---|---|
{{request.method}} |
HTTP verb (GET, POST, …) |
{{request.path}} |
Full request path |
{{request.params.<name>}} |
Path parameter captured by :name in match.path |
{{request.query.<name>}} |
URL query-string value |
{{request.headers.<name>}} |
Request header value (case-insensitive) |
{{request.body.<dot.path>}} |
Dot-path into the parsed JSON request body |
| Token | Value |
|---|---|
{{tenant}} |
Tenant identifier the request was routed to |
{{requestId}} |
Per-request UUID assigned by mockstar |
| Token | Value |
|---|---|
{{faker.uuid}} |
Random UUID v4 |
{{faker.email}} |
Random email address |
{{faker.name}} |
Random full name |
{{faker.integer(min, max)}} |
Random integer in [min, max] |
{{faker.pick(["a","b","c"])}} |
Random element from the array |
{{faker.boolean}} |
true or false |
{{faker.dateIso}} |
Random recent date as ISO 8601 string |
| Token | Value |
|---|---|
{{id("prefix_", 14)}} |
prefix_ + 14 random base62 chars — e.g. order_4OwxzMjhPIt4YQ |
{{id("prefix_", 14, "0123456789abcdef")}} |
Same with a custom alphabet (hex shown) |
{{id.named("key", "prefix_", 14)}} |
Mint once per request per name — repeated calls with the same key return the identical value, useful when the same ID appears in multiple fields |
| Token | Value |
|---|---|
{{now.unix}} |
Current time as Unix seconds (number) |
{{now.millis}} |
Current time as Unix milliseconds (number) |
{{now.iso}} |
Current time as ISO 8601 string |
In --deterministic mode (MOCKSTAR_DETERMINISTIC=1) all faker and now.* tokens return fixed seed-derived values so CI replays are byte-identical.
Full reference including type-preservation rules and worked examples: docs/TIER2.md.
Attach webhooks: [...] to any mock entry to fire HTTP deliveries after the matched response flushes — useful for verifying receiver-side webhook handlers in integration tests, dogfooding partner integrations, and modeling provider behavior in fixtures.
Capabilities at a glance:
| Configuration channels | Per-route url, {{ env.NAME }} interpolation, admin API, opt-in X-Mockstar-Webhook-Url request header (gated by --allow-webhook-url-header) |
| Delivery contract | At-least-once within queue + circuit bounds, exponential backoff with jitter (default [1s, 2s, 4s, 8s, 16s]), idempotent X-Mockstar-Delivery-Id header |
| Signing | Opt-in HMAC-SHA256, Stripe-style ${ts}.${rawBody} payload, secrets via {{ env.X }} or file:/path (inline rejected at config-load) |
| Reliability | Per-webhook circuit breaker, drop-oldest queue cap, per-attempt AbortSignal.timeout, optional expectResponse: { status, body } body assertion |
| Observability | /__admin/tenants/:t/webhooks list (secrets redacted), /webhooks/journal history, POST /webhooks/await?id=… for sync test assertions, POST /webhooks/:id/replay for recovery |
| Metrics | webhook_delivery_total{outcome}, webhook_delivery_latency_us, webhook_queue_depth, webhook_queue_dropped_total, webhook_circuit_state |
| Distribution | In-process (no Redis); single-binary and SDET-embed friendly |
Full guide, decisions log, and security model: docs/webhooks/README.md, docs/webhooks/DECISIONS.md, docs/webhooks/SECURITY.md. Worked example loadable via bun run dev: examples/mocks/default/webhooks-example.json.
See CHANGELOG.md. TL;DR: static + dynamic + pass-through + scenarios + outbound webhooks + OpenAPI import + admin read endpoints + multi-tenancy ship in v1. Stateful mocks, GraphQL, gRPC, fault injection, and a config-mutation admin API are explicitly deferred to v1.1.
Point real HTTPS traffic at your local mocks with zero application code changes — https://api.razorpay.com resolves to 127.0.0.1, terminates on a mkcert-trusted leaf, and forwards to mockstar-on-3000. macOS + Linux only in v1.
mockstar proxy install # one-time: installs CA, DNS, port-bind grant
mockstar proxy start # runs the HTTPS listener on :443See docs/PROXY.md for the full guide and docs/PROXY-RECOVERY.md for incident recovery.
The image is published multi-arch (linux/amd64 + linux/arm64), public, and signed:
docker pull ghcr.io/dhanesh/mockstar:latest # or a pinned tag, e.g. :v0.1.0-alpha.1The image is lean — it ships no mocks. You mount your mock content at run time. The
default command serves /config/mocks; named JS handlers (if any) are read from
/config/handlers. It listens on port 3000 and runs as a non-root user (uid 10001).
Point a host folder at /config/mocks (and /config/handlers if you use named handlers):
docker run --rm -p 3000:3000 \
-v "$PWD/mocks:/config/mocks:ro" \
-v "$PWD/handlers:/config/handlers:ro" \
ghcr.io/dhanesh/mockstar:latest
# in another shell:
curl localhost:3000/health # {"status":"ok"}
curl localhost:3000/users/123 # your mocked responseHardened run (read-only root filesystem + writable temp), matching the Helm chart's security posture:
docker run --rm -p 3000:3000 \
--read-only --tmpfs /tmp \
-v "$PWD/mocks:/config/mocks:ro" \
ghcr.io/dhanesh/mockstar:latestThe mounted folder follows the Config layout — one sub-directory per tenant, one JSON file per resource:
mocks/
default/ # the default tenant
users.json
orders.json
billing.json # ← drop in new files to add more routes
acme/ # add a directory to add a tenant
users.json
handlers/ # optional named JS/TS handlers referenced by dynamic mocks
computeTotal.ts
Add a file (or tenant directory) under your mounted mocks/ folder and it's served — no
image rebuild. Mock JSON changes are hot-reloaded on the mounted volume; named handler
changes require a container restart (handlers are loaded at boot).
For a self-contained, versioned fixture set your team/CI can pull and run with no mount, build a thin image on top of the published base:
# Dockerfile
FROM ghcr.io/dhanesh/mockstar:latest
# Copy your fixtures in (owned by the non-root runtime user)
COPY --chown=10001:10001 mocks/ /config/mocks/
COPY --chown=10001:10001 handlers/ /config/handlers/ # omit if you have no handlers
# Base image already sets ENTRYPOINT + CMD (serve /config/mocks on :3000)docker build -t ghcr.io/your-org/team-fixtures:1.0 .
docker run --rm -p 3000:3000 ghcr.io/your-org/team-fixtures:1.0 # mocks are baked inPin the base by digest (FROM ghcr.io/dhanesh/mockstar@sha256:…) for reproducible builds,
push the result to your own registry, and every consumer gets the identical mock set.
Deploy the mock + webhook server with the chart in charts/mockstar — secure-by-default (read-only root FS, non-root uid 10001, dropped caps, admin token via Secret).
helm install mockstar oci://ghcr.io/dhanesh/charts/mockstar \
--version 0.1.0 --namespace mockstar --create-namespace- Single replica by design (
replicaCount: 1,Recreate): the journal, scenario state, and webhook queue are in-memory and per-pod — scaling beyond 1 splits that state. - The TLS-intercepting
mockstar proxyis a local-only experimental dev tool and is not deployed by the chart.
Full guide — tenant ConfigMaps, handlers, admin token Secret, NetworkPolicy egress, probes/metrics, image-by-digest: docs/deployment/helm.md.
See docs/SECURITY.md for the threat model, SECURITY.md for the security policy and posture. Admin endpoints are disabled by default. Pass-through and OpenAPI import share a hardened URL validator that rejects private-range targets by default (addressing CVE-2026-39885-class OpenAPI $ref attacks).
MIT — see LICENSE. Governance policy, irreversible-decision watch list, and the protocol for changing project-level commitments live in docs/GOVERNANCE.md.
{ "id": "orders-create", "match": { "method": "POST", "path": "/orders" }, "response": { "kind": "static", "status": 201, "body": { "id": "{{id(\"ord_\", 12)}}" } }, "webhooks": [{ "id": "order-created", "url": "https://api.partner.example/hooks/order-created", "method": "POST", "headers": { "content-type": "application/json" }, "body": { "orderId": "{{request.body.orderId}}", "tenant": "{{tenant}}" } }] }