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 '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 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.
See docs/SECURITY.md for the threat model. 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}}" } }] }