Mockwave is an open-source, multi-protocol mock server. Define rules and simulations in JSON, manage them through the browser UI, or let an AI assistant do it — Mockwave responds to HTTP, GraphQL, SOAP, and gRPC requests with weighted traffic splitting, dynamic JavaScript responses, real-time metrics, and a built-in MCP server for Claude Code integration.
- Multi-protocol — HTTP REST, GraphQL, SOAP, gRPC (reflection-free, descriptor-based)
- Traffic splitting — weighted buckets per rule (e.g., 90% mock / 10% forward to real service)
- Dynamic scripting — per-simulation JavaScript (goja) for computed responses
- Real-time admin UI — browser dashboard at
localhost:9090for rule/simulation CRUD, live metrics, and unmatched request capture - Multiple store backends — JSON file, DynamoDB, MongoDB, Azure Cosmos DB (MongoDB API)
- Hot reload — update rules without restarting via the admin API
- Import/Export — export rules + simulations to a runnable JSON config from the admin UI/API; two-phase import with conflict override (remote stores; with the JSON store the config file already plays this role)
- Chaos testing with profiles — inject faults (latency, errors, resets, truncation, throttling) at the boundary; profile chaos testing with realistic scenarios (degraded services, network partitions, cascading failures) without host agents or root access
- Matched request capture — opt-in capture of requests that matched a rule, retrievable via
GET /api/matched/{rule_id}(paginated; filter by method/path/status/header) andGET /api/matched/{rule_id}/{id}(full request + bodies). Global TTL with native expiry on DynamoDB/Mongo/Cosmos. Enables regressive e2e: assert both the mock's response and the exact request your system sent. - Outgoing event capture (AWS) — intercept the app's SNS / SQS / EventBridge publishes on the mock port, capture them for assertion, and either return a valid mock response or forward to the real broker (re-signed with your credentials, relaying the broker's real id). Point your AWS SDK client at Mockwave via endpoint override (
--protocols http,aws --event-capture). Seedocs/event-capture.md. Event rules and captures persist on DynamoDB/MongoDB/Cosmos like matched capture. - AI integration (MCP) —
mockwave mcpexposes a Model Context Protocol server so Claude Code can create rules, manage simulations, and auto-generate mocks from any OpenAPI 2.0/3.0 spec - Embeddable library — public
store.DataStore,observability.Logger/Tracer/MetricsRecorderinterfaces; bring your own backends
brew tap lfdubiela/mockwave
brew trust lfdubiela/mockwave
brew install mockwave # or: brew upgrade mockwave (if already installed)
# Verify installation
mockwave versionNote:
brew trust lfdubiela/mockwaveis only needed if Homebrew prompts to trust the tap. Skip it if no prompt appears.
# Create a minimal config
cat > config.json <<'EOF'
{
"rules": [
{
"id": "hello",
"name": "Hello World",
"match": { "method": "GET", "path": "/hello" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "hello-sim" }]
}
],
"simulations": [
{
"id": "hello-sim",
"protocol": "http",
"response": { "status": 200, "body": { "message": "Hello from Mockwave!" } }
}
]
}
EOF
# Start on default ports (mock :8080, admin :9090)
mockwave start -f config.json
# Custom ports
mockwave start -f config.json --port 3000 --admin-port 3001# Test it
curl http://localhost:8080/hello
# {"message":"Hello from Mockwave!"}
# Open the admin UI
open http://localhost:9090# Upgrade
brew upgrade mockwaveWith Mockwave running, add to ~/.claude/mcp.json (create if it doesn't exist):
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
}
}
}Then ask Claude Code to do the work:
"Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json"
"Create a mock for POST /checkout that returns 201 with an order ID"
"What requests are hitting mockwave but not matching any rule?"
# Download the latest release binary (replace OS/ARCH as needed)
curl -Lo mockwave https://github.com/lfdubiela/mockwave/releases/download/v0.2.0/mockwave-linux-amd64
chmod +x mockwave
# Start the server
./mockwave start -f config.jsondocker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.jsonA single pod served ~72,000 req/s in testing, so most deployments need
cpu: 100m / memory: 64Mi and nothing more. Set GOMAXPROCS explicitly —
the Go runtime does not reliably infer a container CPU limit, and getting
this wrong causes latency spikes at low CPU utilisation.
→ Kubernetes guide: sizing, reference manifest, probes, multi-replica rule sync
mockwave [command]
Commands:
start Start the mock server
validate Validate a config file without starting the server
version Print version
mcp Start MCP server for AI assistant integration (Claude Code, etc.)
Flags (start):
-f, --config string Path to JSON config file (required for --store=json)
--port int Mock server port (default 8080)
--admin-port int Admin UI/API port (default 9090)
--protocols string Comma-separated: http,graphql,soap,grpc,aws (default "http")
--grpc-port int gRPC server port (default 50051)
--grpc-proto string Path to compiled .pb descriptor for gRPC proto conversion
# Store backend
--store string Storage backend: json|dynamodb|mongo|cosmos (default "json")
--reload-interval duration Version-poll reload interval for remote stores (default 15s)
# DynamoDB
--dynamo-rules-table string DynamoDB table for rules (default "mockwave-rules")
--dynamo-sims-table string DynamoDB table for simulations (default "mockwave-simulations")
--dynamo-faults-table string DynamoDB table for fault profiles (default "mockwave-fault-profiles")
--dynamo-scenarios-table string DynamoDB table for scenarios (default "mockwave-scenarios")
--dynamo-region string AWS region (default "us-east-1")
--dynamo-endpoint string Custom endpoint, e.g. http://localhost:8000
# MongoDB
--mongo-uri string MongoDB connection URI (default "mongodb://localhost:27017")
--mongo-db string MongoDB database name (default "mockwave")
# Cosmos DB
--cosmos-uri string Cosmos DB connection string (MongoDB API)
--cosmos-db string Cosmos DB database name (default "mockwave")
# Matched request capture
--matched-capture Enable matched request capture (default false)
--matched-ttl int Capture TTL in seconds (default 3600)
--matched-buffer-size int In-memory ring buffer capacity (default 10000)
--matched-sync-interval int Write-behind sync interval in seconds (default 30)
# AWS event capture (use with --protocols aws)
--event-capture Enable AWS event interception + capture (default false)
--event-ttl int Capture TTL in seconds (default 3600)
--event-buffer-size int In-memory buffer capacity (default 10000)
--event-sync-interval int Write-behind sync interval in seconds (default 30)
# HTTP + GraphQL on the same port
mockwave start -f config.json --protocols http,graphql
# All protocols
mockwave start -f config.json --protocols http,graphql,soap,grpc --grpc-proto service.pb
# DynamoDB backend (uses default AWS credential chain)
mockwave start --store dynamodb --dynamo-region eu-west-1
# Local DynamoDB (e.g. DynamoDB Local)
mockwave start --store dynamodb --dynamo-endpoint http://localhost:8000
# MongoDB backend
mockwave start --store mongo --mongo-uri mongodb://user:pass@host:27017/mydb
# Validate a config file
mockwave validate config.jsonThe JSON config file has two top-level arrays: rules and simulations.
{
"rules": [ ...Rule... ],
"simulations": [ ...Simulation... ]
}{
"id": "string (required, unique)",
"name": "string (display label)",
"match": {
"protocol": "http | graphql | soap | grpc",
"method": "GET | POST | PUT | DELETE | PATCH | ...",
"path": "/users/* (glob supported)",
"headers": { "X-Tenant": "acme" },
"query": { "version": "2" },
"body": { "$.type": "order" }
},
"buckets": [
{
"weight": 100,
"action": "simulate | forward",
"simulation_id": "sim-id (required when action=simulate)"
}
],
"forward_url": "https://real-api.example.com (required when any bucket has action=forward)"
}Path globs: * matches a single segment, ** matches any number of segments.
/users/*matches/users/123but not/users/123/orders/api/**matches any path under/api/
Traffic splitting: weights are relative. [{weight:90,…}, {weight:10,…}] routes 90% to the first bucket and 10% to the second. Weights do not need to sum to 100.
Forwarding: set action: "forward" and provide forward_url. The request is proxied to forward_url + original_path with original headers and body.
{
"id": "string (required, unique)",
"protocol": "http | graphql | soap | grpc",
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": { "any": "JSON value" },
"delay_ms": 150
},
"script": "// optional JS — return value overrides response.body\nreturn { computed: request.path };",
"soap_envelope": "<soap:Envelope>...</soap:Envelope>",
"grpc_message": "{ \"userId\": \"123\" }",
"grpc_status": 0
}Enabled by default. Routes by method + path. Response body supports Go template variables:
{
"id": "user-get",
"protocol": "http",
"response": {
"status": 200,
"body": { "id": "{{.PathParam \"id\"}}", "name": "Alice" }
}
}Enable with --protocols http,graphql. Mockwave parses the operationName from the request body and matches against match.path (treated as operation name prefix/glob).
{
"id": "gql-user",
"match": { "protocol": "graphql", "path": "GetUser" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "gql-user-sim" }]
}Enable with --protocols http,soap. Mockwave reads the SOAP action from the SOAPAction header and routes accordingly. Set soap_envelope in the simulation to return a raw XML envelope.
{
"id": "soap-create-order",
"match": { "protocol": "soap", "path": "CreateOrder" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "create-order-sim" }]
}{
"id": "create-order-sim",
"protocol": "soap",
"soap_envelope": "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><CreateOrderResponse><orderId>42</orderId></CreateOrderResponse></soap:Body></soap:Envelope>"
}Enable with --protocols http,grpc. Requires a compiled protobuf descriptor:
# Compile your .proto to a descriptor
protoc --descriptor_set_out=service.pb --include_imports service.proto
# Start with the descriptor
mockwave start -f config.json --protocols http,grpc --grpc-proto service.pbSet grpc_message (JSON representation of the proto response) and grpc_status (gRPC status code, 0 = OK) in the simulation:
{
"id": "get-user-sim",
"protocol": "grpc",
"grpc_message": "{ \"userId\": \"abc\", \"name\": \"Alice\" }",
"grpc_status": 0
}| Backend | Flag | Notes |
|---|---|---|
| JSON file | --store json -f config.json |
Default. File is read on start; hot-reloaded via admin API. |
| DynamoDB | --store dynamodb |
Uses AWS default credential chain. Tables must exist with PK id (String). |
| MongoDB | --store mongo |
Tested with MongoDB 6+. |
| Cosmos DB | --store cosmos |
Uses MongoDB wire protocol. ssl=true and retryWrites=false applied automatically. |
Create two tables (substitute region/table names as needed):
aws dynamodb create-table --table-name mockwave-rules \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
aws dynamodb create-table --table-name mockwave-simulations \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUESTOpen http://localhost:9090 in a browser. The UI is served from the admin port and requires no external dependencies.
| Tab | What it does |
|---|---|
| Rules | List, create, edit, and delete rules |
| Simulations | List, add (JSON editor), and delete simulations |
| Metrics | Live request counters and per-rule hit rates (SSE, updates every second) |
| Unmatched | Requests that matched no rule — click "Create Rule" to pre-fill the rule form |
All endpoints are on the admin port (default :9090).
| Method | Path | Description |
|---|---|---|
GET |
/api/rules |
List all rules |
POST |
/api/rules |
Create a rule |
GET |
/api/rules/:id |
Get a rule |
PUT |
/api/rules/:id |
Update a rule |
DELETE |
/api/rules/:id |
Delete a rule |
GET |
/api/simulations |
List all simulations |
POST |
/api/simulations |
Create a simulation |
GET |
/api/simulations/:id |
Get a simulation |
PUT |
/api/simulations/:id |
Update a simulation |
DELETE |
/api/simulations/:id |
Delete a simulation |
GET |
/api/openapi.json |
OpenAPI 3.0 spec (JSON) |
GET |
/api/metrics |
Current metrics snapshot (JSON) |
GET |
/api/metrics/stream |
SSE stream — one event per second |
GET |
/api/unmatched |
List captured unmatched requests |
DELETE |
/api/unmatched |
Clear unmatched request buffer |
POST |
/api/reload |
Trigger hot-reload from store |
GET |
/api/health |
{"status":"ok"} |
{
"at": "2026-05-24T12:00:00Z",
"total_requests": 1042,
"misses": 13,
"rules": [
{
"rule_id": "hello",
"rule_name": "Hello World",
"hits": 1029,
"p95_ms": 42.3
}
]
}total_requests, misses and hits count every request since start.
Per-rule percentiles (p50_ms, p95_ms) are computed over a recent
window — the last 2048 requests for that rule — not over all requests since
start. Retaining every sample would grow without bound, and the snapshot sorts
those samples under the lock the request path uses, so an unbounded window
degraded tail latency the longer a process ran. Treat the percentiles as "how
this rule is responding lately"; use hits when you need an exact count.
mockwave mcp exposes a Model Context Protocol server that lets Claude Code (and other MCP-compatible AI assistants) create, inspect, and delete rules and simulations on any Mockwave instance — local or remote.
Claude Code
│ stdio (stdin/stdout)
▼
mockwave mcp (local process, spawned by Claude Code)
│ HTTP
▼
Mockwave Admin API (:9090) — localhost OR remote (EKS, sandbox, etc.)
mockwave mcp runs locally and bridges MCP tool calls to HTTP requests against --admin-url. The admin URL can point anywhere — a local dev instance or a shared sandbox running in the cloud.
Ensure mockwave is on your $PATH (e.g. via brew install mockwave), then add to ~/.claude/mcp.json:
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
}
}
}Multiple instances are supported — Claude Code namespaces the tools automatically:
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
},
"mockwave-sandbox": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "https://mockwave.sandbox.example.com"]
}
}
}Security: The Mockwave admin API has no authentication. When pointing
--admin-urlat a remote instance, ensure the admin port is protected by a firewall or reverse proxy.
Rules
| Tool | Description |
|---|---|
list_rules |
List all rules |
get_rule |
Get a rule by ID |
create_rule |
Create a new rule |
update_rule |
Replace a rule |
delete_rule |
Delete a rule |
Simulations
| Tool | Description |
|---|---|
list_simulations |
List all simulations |
get_simulation |
Get a simulation by ID |
create_simulation |
Create a new simulation |
update_simulation |
Replace a simulation |
delete_simulation |
Delete a simulation |
generate_from_openapi |
Auto-generate rules + simulations from an OpenAPI 2.0/3.0 spec (URL or file) |
Event Rules
| Tool | Description |
|---|---|
list_event_rules |
List all event rules |
get_event_rule |
Get an event rule by ID |
create_event_rule |
Create a new event rule |
update_event_rule |
Replace an existing event rule |
delete_event_rule |
Delete an event rule |
Event Captures
| Tool | Description |
|---|---|
list_event_captures |
List captured events for a rule (supports body/attr/query filters) |
get_event_capture |
Get the full detail of a single captured event (including bodies) |
clear_event_captures |
Delete all captured events for a rule |
Matched (HTTP) Requests
| Tool | Description |
|---|---|
list_matched |
List matched HTTP requests for a rule (supports body/query filters) |
get_matched |
Get the full detail of a single matched HTTP request (including bodies) |
clear_matched |
Delete all matched HTTP requests for a rule |
Fault Profiles (Chaos)
| Tool | Description |
|---|---|
list_faults |
List all fault profiles |
get_fault |
Get a fault profile by ID |
create_fault |
Create a new fault profile (types: jitter/error/hang/reset/halfResponse/slowBody/retryStorm) |
update_fault |
Replace an existing fault profile |
delete_fault |
Delete a fault profile |
Chaos Control
| Tool | Description |
|---|---|
halt_chaos |
Pause all active chaos faults immediately (kill-switch) |
resume_chaos |
Resume chaos fault injection after a halt |
get_chaos_status |
Get current chaos state: halted flag and active-scenario name |
Scenarios
| Tool | Description |
|---|---|
list_scenarios |
List all chaos scenarios |
get_scenario |
Get a chaos scenario by ID |
create_scenario |
Create a new chaos scenario |
update_scenario |
Replace an existing chaos scenario |
delete_scenario |
Delete a chaos scenario |
start_scenario |
Start a chaos scenario (applies fault phases to targeted rules) |
stop_scenario |
Stop a running chaos scenario (restores normal rule behavior) |
Config Import/Export
| Tool | Description |
|---|---|
export_config |
Export the full Mockwave config (rules, simulations, fault profiles, scenarios) as JSON |
import_config |
Bulk-import a config; use preview:true for a dry-run conflict report |
Dev / Debugging
| Tool | Description |
|---|---|
eval_script |
Evaluate a JS simulation script against a sample request to test computed responses |
get_metrics_history |
Get the rolling metrics history (recent traffic time-series) |
Observability
| Tool | Description |
|---|---|
get_metrics |
Current metrics snapshot |
list_unmatched |
List requests that matched no rule |
clear_unmatched |
Clear the unmatched buffer |
reload |
Trigger hot-reload from store |
health |
Check admin API reachability |
Create a rule from a natural language description:
You: "Create a mock for GET /orders that returns 200 with an empty orders array"
Claude calls create_simulation → POST /api/simulations
Claude calls create_rule → POST /api/rules
Claude: "Done. GET http://localhost:8080/orders now returns {"orders":[]}"
Generate mocks from an existing OpenAPI spec:
You: "Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json"
Claude calls generate_from_openapi with the URL
Claude: "Created 18 rules and 18 simulations covering all Petstore endpoints.
GET /pet/{petId} → 200 {"id":1,"name":"doggie","status":"available"}
POST /pet → 201 {"id":1,"name":"doggie"}
DELETE /pet/{petId} → 200
..."
Inspect what's being hit and fix gaps:
You: "What requests are hitting mockwave but not matching any rule?"
Claude calls list_unmatched
Claude: "3 unmatched requests found:
POST /api/v2/checkout ← no rule
GET /api/v2/cart/99 ← no rule
Want me to create mocks for these?"
Dynamic script mock via MCP:
You: "Create a mock for GET /users/:id that echoes the ID back in the response"
Claude calls create_simulation with script:
const id = request.path.split('/').pop();
return { body: { id: id, name: "User " + id } };
Claude calls create_rule → POST /api/rules
Claude: "Done. GET /users/42 now returns {"id":"42","name":"User 42"}"
Intercept and forward SNS publishes to real AWS:
You: "Intercept SNS publishes to the orders topic and forward them to real AWS"
Claude calls create_event_rule with match.service=sns, match.target=*:orders,
and a forward block pointing at the real SNS endpoint with static credentials.
Claude: "Event rule created. SNS publishes to *:orders will be captured and
re-signed to real AWS. Check captures with list_event_captures."
Query event captures by correlation ID:
You: "What did my app publish with correlation_id abc-123?"
Claude calls list_event_captures with body filter $.correlation_id:abc-123
Claude: "Found 2 captures for rule 'order-placed-sns':
01J8X... — SNS Publish to arn:...:order-placed at 10:00:01Z
01J8Y... — SNS Publish to arn:...:order-placed at 10:00:05Z
Want me to fetch the full message body for either one?"
Create and run a chaos scenario:
You: "Create a chaos scenario that injects 500 errors on /orders for 30 seconds, then start it"
Claude calls create_fault with type=error, status=500, targeting the /orders rule.
Claude calls create_scenario with a single phase using that fault, duration=30s.
Claude calls start_scenario.
Claude: "Scenario started. /orders will return 500 for the next 30 seconds.
Call get_chaos_status to monitor, or halt_chaos to stop immediately."
Inspect matched HTTP requests sent to a mock:
You: "Show me the requests my service sent to the payments mock"
Claude calls list_matched for the payments rule
Claude: "Last 5 matched requests to rule 'payments':
POST /v1/charges body: {"amount":4999,"currency":"usd"} at 10:01Z
POST /v1/charges body: {"amount":1200,"currency":"usd"} at 10:02Z
..."
Drop this in your project's CLAUDE.md to make Claude aware of Mockwave automatically:
## Mocking
Mockwave is running at http://localhost:8080 (admin: http://localhost:9090).
Use the `mockwave-local` MCP tools to create or update mocks instead of hardcoding responses.
When a test hits an unmocked endpoint, call `list_unmatched` and create the missing rule.Set "script" on any simulation to run JavaScript (via goja) on every matched request. The script must return an object with at least a body key (and optionally status, headers, delay_ms) to override the response.
{
"id": "dynamic-user",
"protocol": "http",
"response": { "status": 200 },
"script": "const id = request.path.split('/').pop(); return { body: { id: id, ts: Date.now() } };"
}The return object can override any part of the response:
return {
status: 201,
headers: { "X-Custom": "value" },
body: { id: request.path.split('/').pop(), ts: Date.now() },
delay_ms: 100
};request.path is the raw path string. Use standard JS string methods to extract segments:
// GET /users/42 → id = "42"
const id = request.path.split('/').pop();
return { body: { id: id } };// GET /org/acme/users/42 → segments = ["org","acme","users","42"]
const parts = request.path.split('/').filter(Boolean);
const org = parts[1]; // "acme"
const id = parts[3]; // "42"
return { body: { org: org, userId: id } };// named segment via regex: /users/:id/orders/:orderId
const match = request.path.match(/\/users\/([^/]+)\/orders\/([^/]+)/);
const userId = match ? match[1] : null;
const orderId = match ? match[2] : null;
return { body: { userId: userId, orderId: orderId } };// numeric ID anywhere in path
const match = request.path.match(/\/(\d+)/);
const id = match ? parseInt(match[1], 10) : null;
return {
status: id ? 200 : 404,
body: id ? { id: id } : { error: "not found" }
};// reflect full path + method back (useful for debugging)
return {
body: {
method: request.method,
path: request.path,
parts: request.path.split('/').filter(Boolean)
}
};// bearer token presence check
const auth = request.headers["authorization"] || "";
const token = auth.replace("Bearer ", "");
return {
status: token ? 200 : 401,
body: token ? { token: token } : { error: "unauthorized" }
};// tenant routing via custom header
const tenant = request.headers["x-tenant-id"] || "default";
const id = request.path.split('/').pop();
return { body: { tenant: tenant, id: id, source: "mock" } };// echo all headers back (debugging)
return { body: { headers: request.headers } };// body is already parsed when Content-Type is application/json
const name = request.body && request.body.name;
return { body: { message: "Hello, " + (name || "stranger") } };// validate required fields, return 422 if missing
const b = request.body || {};
if (!b.email || !b.name) {
return { status: 422, body: { error: "email and name are required" } };
}
return { status: 201, body: { id: Math.floor(Math.random() * 10000), email: b.email } };// POST /accounts/:accountId/transfers
const accountId = request.path.split('/').filter(Boolean)[1];
const requestId = request.headers["x-request-id"] || "none";
const amount = request.body && request.body.amount;
return {
status: 202,
headers: { "x-request-id": requestId },
body: {
transferId: "txn-" + Date.now(),
from: accountId,
amount: amount,
status: "pending"
}
};Available in the script context:
| Variable | Type | Description |
|---|---|---|
request.method |
string | HTTP method ("GET", "POST", …) |
request.path |
string | Full path string (e.g. "/orders/12345") |
request.headers |
object | Request headers (lowercase keys) |
request.body |
object|null | Parsed JSON body, or null |
response.status |
number | Current response status (modifiable) |
response.body |
object | Current response body (modifiable) |
Mockwave doubles as an API-level chaos tool: instead of breaking real infrastructure (Gremlin-style host agents), it injects failures at the boundary your client sees — mock responses and forwarded upstream traffic. No agent, no root; runs locally and in CI.
Define a fault profile (a reusable set of failure modes), attach it to a rule
bucket via fault_profile_id, and tune the blast radius with the bucket weight
and each fault's probability. Seven fault types are available:
| Type | Effect |
|---|---|
jitter |
latency: base_delay_ms + random [0, jitter_ms) |
error |
short-circuit with a custom HTTP status_code / body / headers |
hang |
blackhole: block max_ms then close with no response |
reset |
TCP RST — client sees a connection reset (dead upstream) |
halfResponse |
write fraction of the body then cut the connection |
slowBody |
bandwidth throttle to bytes_per_sec (modifier) |
retryStorm |
fail the first fail_first requests per key, then recover |
jitter/slowBody are modifiers (combine with anything); the rest are terminal
(first one to fire wins). All types work on REST/GraphQL/SOAP; gRPC is not yet
supported. A global kill switch suppresses everything instantly, and
scenarios chain profiles into timed phases for drills.
Quick example — 30% of /orders traffic gets a 503:
curl -X POST localhost:9090/api/faults -d '{
"id":"flaky-orders","name":"Flaky orders","enabled":true,
"faults":[{"type":"error","probability":0.3,"params":{"status_code":503,"body":"{\"error\":\"boom\"}"}}]}'
# then attach "fault_profile_id":"flaky-orders" to the /orders rule's bucket→ Full Chaos Testing guide: every fault type, UI/CLI/API walkthroughs, scenarios, API reference
All on the admin port (default :9090). Full details and schemas in the
chaos guide.
| Method | Path | Purpose |
|---|---|---|
GET POST |
/api/faults |
list / create fault profiles |
GET PUT DELETE |
/api/faults/{id} |
get / update / delete a profile (409 if referenced) |
POST |
/api/chaos/halt · /api/chaos/resume |
kill switch off / on |
GET |
/api/chaos/status |
{halted, active_scenario} |
GET POST |
/api/scenarios |
list / create scenarios |
GET PUT DELETE |
/api/scenarios/{id} |
get / update / delete a scenario |
POST |
/api/scenarios/{id}/start · /stop |
start (202, 409 if one runs) / stop |
Equivalent CLI: mockwave fault …, mockwave chaos halt|resume|status,
mockwave scenario list|start|stop (all accept --admin-url).
Fault profiles and scenarios persist on every backend (JSON file, DynamoDB,
MongoDB, Cosmos). DynamoDB needs two extra tables (mockwave-fault-profiles,
mockwave-scenarios, PK id); Mongo/Cosmos collections auto-create. See the
guide for details.
Mockwave exposes public Go interfaces for storage and observability. Implement any of them and pass to server.New:
| Interface | Controls |
|---|---|
store.DataStore |
Where rules and simulations are stored |
observability.Logger |
Structured logging (zerolog, zap, …) |
observability.Tracer |
Distributed tracing (OpenTelemetry, …) |
observability.MetricsRecorder |
Request metrics (Prometheus, …) |
All fields in server.Config default to Noop implementations when nil — safe to omit anything you don't need.
→ Extension guide: DataStore + observability interfaces, full examples
# Run with a local config file
docker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.json
# All protocols
docker run -p 8080:8080 -p 9090:9090 -p 50051:50051 \
-v $(pwd)/config.json:/config.json \
-v $(pwd)/service.pb:/service.pb \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.json --protocols http,graphql,soap,grpc --grpc-proto /service.pb
# DynamoDB backend (IAM role or env vars)
docker run -p 8080:8080 -p 9090:9090 \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
-e AWS_REGION=us-east-1 \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start --store dynamodbdocker build -t mockwave:local .
docker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
mockwave:local start -f /config.jsonRequirements: Go 1.21+
git clone https://github.com/lfdubiela/mockwave.git
cd mockwave
# Build binary
make build # outputs ./mockwave
# Run tests
make test
# Check coverage (must be ≥80%)
make coveragePlanned-but-not-yet-built work — including scope deliberately deferred from shipped features — is tracked in docs/roadmap.md.
Contributions are welcome. Please open an issue before submitting a large PR.
- Fork the repo
- Create a feature branch (
git checkout -b feat/my-feature) - Write tests first (TDD)
- Ensure
make testandmake coveragepass - Submit a pull request
Mockwave is released under the MIT License. Free to use, modify, and distribute — commercially or otherwise — with attribution.