Skip to content

WebSocket Mocking

WebSocket mocking enables testing of real-time bidirectional communication without connecting to actual backend services. mockd provides full WebSocket support with message matching, response templating, and scripted scenarios.

Use WebSocket mocks when you need to:

  • Test chat applications, notifications, or live updates
  • Simulate real-time data feeds (stock prices, sports scores, IoT sensors)
  • Develop frontends before backend WebSocket services are ready
  • Create reproducible test scenarios for bidirectional protocols
  • Debug client-side WebSocket handling

Create a minimal WebSocket mock:

version: "1.0"
mocks:
- id: simple-ws
name: Simple WebSocket
type: websocket
websocket:
path: /ws
echoMode: true

Start the server and connect:

Terminal window
# Start mockd
mockd serve --config mockd.yaml
# Connect with the mockd CLI
mockd websocket connect ws://localhost:4280/ws
# Or use wscat
wscat -c ws://localhost:4280/ws

In echo mode, any message you send is echoed back.

version: "1.0"
mocks:
- id: ws-full-example
name: Full WebSocket Example
type: websocket
enabled: true
websocket:
# Required: endpoint path
path: /ws/chat
# Subprotocol negotiation
subprotocols:
- chat.v1
- chat.v2
requireSubprotocol: true # Reject connections without matching subprotocol
# Connection limits
maxMessageSize: 65536 # Maximum message size in bytes
maxConnections: 100 # Maximum concurrent connections
idleTimeout: "5m" # Disconnect after inactivity
# Echo mode: reflect messages back to sender
echoMode: false
# Heartbeat/keepalive
heartbeat:
enabled: true
interval: "30s" # Send ping every 30 seconds
timeout: "10s" # Disconnect if no pong within 10 seconds
# Message matchers (evaluated in order)
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
# Default response when no matcher matches
defaultResponse:
type: json
value:
error: "Unknown message"
# Scripted scenario (optional)
scenario:
name: welcome-flow
steps:
- type: send
message:
type: json
value: { "type": "welcome" }
FieldTypeDescription
pathstringEndpoint path (required)
subprotocolsstring[]Supported WebSocket subprotocols
requireSubprotocolbooleanReject connections without matching subprotocol
maxMessageSizeintegerMaximum message size in bytes
maxConnectionsintegerMaximum concurrent connections
idleTimeoutdurationConnection idle timeout (e.g., ”30s”, “5m”)
echoModebooleanEcho received messages back to client
heartbeatobjectPing/pong keepalive configuration
matchersarrayMessage matchers with responses
defaultResponseobjectResponse when no matcher matches
scenarioobjectScripted message sequence

Matchers define how to respond to incoming WebSocket messages. Each matcher has a match criteria and response configuration.

Match the entire message exactly:

matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"

Match if the message contains a substring:

matchers:
- match:
type: contains
value: "hello"
response:
type: text
value: "Hello from mockd!"

Match if the message starts with a prefix:

matchers:
- match:
type: prefix
value: "CMD:"
response:
type: text
value: "Command received"

Match if the message ends with a suffix:

matchers:
- match:
type: suffix
value: "?"
response:
type: text
value: "That's a question!"

Match using a regular expression:

matchers:
- match:
type: regex
value: "user_\\d+"
response:
type: json
value:
matched: true
pattern: "user ID"

Match specific fields in JSON messages using JSONPath:

matchers:
# Match messages where $.type equals "ping"
- match:
type: json
path: "$.type"
value: "ping"
response:
type: json
value:
type: "pong"
timestamp: "{{now}}"
# Match messages where $.action equals "subscribe"
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
channel: "{{message.channel}}"

Filter by WebSocket message type (text or binary):

matchers:
- match:
type: exact
value: "ping"
messageType: text # Only match text messages
response:
type: text
value: "pong"
- match:
type: regex
value: ".*"
messageType: binary # Only match binary messages
response:
type: text
value: "Binary message received"

Match without sending a response (useful for logging or scenario progression):

matchers:
- match:
type: json
path: "$.type"
value: "heartbeat"
noResponse: true

Matchers are evaluated in order. The first matching rule wins:

matchers:
# Specific match first
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
# Generic match later
- match:
type: regex
value: ".*"
response:
type: text
value: "Unknown command"
response:
type: text
value: "Hello, World!"
response:
type: json
value:
status: "ok"
timestamp: "{{now}}"
data:
message: "Welcome!"
response:
type: binary
value: "SGVsbG8gV29ybGQh" # Base64 encoded

Add artificial latency:

response:
type: json
value:
status: "processed"
delay: "500ms"

Use template expressions in responses:

response:
type: json
value:
id: "{{uuid}}"
timestamp: "{{now}}"
echo: "{{message}}" # Echo the received message

Available template variables:

ExpressionDescription
{{message}}The received message content
{{now}}Current ISO timestamp
{{timestamp}}Unix timestamp (seconds)
{{uuid}}Random UUID

Define a fallback for unmatched messages:

websocket:
path: /ws
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
defaultResponse:
type: json
value:
type: "error"
message: "Unknown command"
received: "{{message}}"

When echoMode: true, messages are echoed back to the client. This is useful for testing client message handling:

websocket:
path: /ws/echo
echoMode: true

Echo mode works alongside matchers. Matchers are checked first; if none match and no default response is configured, the message is echoed.

Scenarios enable scripted message sequences for complex testing flows.

websocket:
path: /ws/onboarding
scenario:
name: welcome-flow
steps:
# Send welcome message immediately on connect
- type: send
message:
type: json
value:
type: "welcome"
message: "Connected to server"
# Wait for client ready signal
- type: wait
match:
type: json
path: "$.type"
value: "ready"
timeout: "10s"
# Send session info
- type: send
message:
type: json
value:
type: "session_start"
sessionId: "{{uuid}}"

Send a message to the client:

- type: send
message:
type: json
value:
event: "notification"
data: "Hello!"

Wait for a client message:

- type: wait
match:
type: json
path: "$.type"
value: "acknowledge"
timeout: "5s"
optional: false # Fail if timeout expires (default)

Pause for a duration:

- type: delay
duration: "2s"

Repeat the scenario when it completes:

scenario:
name: heartbeat-loop
loop: true
steps:
- type: delay
duration: "5s"
- type: send
message:
type: json
value:
type: "heartbeat"
timestamp: "{{now}}"

Reset scenario state when a client reconnects:

scenario:
name: tutorial
resetOnReconnect: true
steps:
- type: send
message:
type: text
value: "Welcome! Let's begin the tutorial..."
version: "1.0"
mocks:
- id: chat-room
name: Chat Room
type: websocket
websocket:
path: /ws/chat
subprotocols:
- chat
- json
maxMessageSize: 65536
idleTimeout: "10m"
maxConnections: 100
heartbeat:
enabled: true
interval: "30s"
timeout: "10s"
matchers:
# Join room
- match:
type: json
path: "$.type"
value: "join"
response:
type: json
value:
type: "joined"
message: "Welcome to the chat room!"
timestamp: "{{now}}"
# Send message
- match:
type: json
path: "$.type"
value: "message"
response:
type: json
value:
type: "message_ack"
id: "{{uuid}}"
timestamp: "{{now}}"
# Leave room
- match:
type: json
path: "$.type"
value: "leave"
response:
type: json
value:
type: "left"
message: "Goodbye!"
# Typing indicator
- match:
type: json
path: "$.type"
value: "typing"
noResponse: true
# Status command
- match:
type: exact
value: "status"
response:
type: json
value:
type: "status"
users: 42
uptime: "{{timestamp}}"
# Help command
- match:
type: exact
value: "help"
response:
type: text
value: |
Available commands:
- {"type": "join", "username": "..."}: Join chat
- {"type": "message", "content": "..."}: Send message
- {"type": "leave"}: Leave chat
- status: Get server status
- help: Show this help
defaultResponse:
type: json
value:
type: "error"
message: "Unknown command. Type 'help' for available commands."
version: "1.0"
mocks:
- id: notifications
name: Push Notifications
type: websocket
websocket:
path: /ws/notifications
heartbeat:
enabled: true
interval: "30s"
matchers:
# Subscribe to channel
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
channel: "{{message.channel}}"
# Unsubscribe
- match:
type: json
path: "$.action"
value: "unsubscribe"
response:
type: json
value:
action: "unsubscribed"
channel: "{{message.channel}}"
# Send periodic notifications
scenario:
name: notification-stream
loop: true
steps:
- type: delay
duration: "10s"
- type: send
message:
type: json
value:
type: "notification"
id: "{{uuid}}"
title: "New update available"
body: "Check out the latest features"
timestamp: "{{now}}"
version: "1.0"
mocks:
- id: stock-ticker
name: Stock Ticker
type: websocket
websocket:
path: /ws/stocks
maxConnections: 1000
matchers:
# Subscribe to symbol
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
symbol: "{{message.symbol}}"
message: "You will receive updates for this symbol"
# Simulate price updates
scenario:
name: price-updates
loop: true
steps:
- type: delay
duration: "1s"
- type: send
message:
type: json
value:
type: "price_update"
symbol: "MOCK"
price: 123.45
change: 1.23
volume: 1000000
timestamp: "{{now}}"
version: "1.0"
mocks:
- id: graphql-ws
name: GraphQL WebSocket
type: websocket
websocket:
path: /graphql
subprotocols:
- graphql-ws
- graphql-transport-ws
requireSubprotocol: true
matchers:
# Connection init
- match:
type: json
path: "$.type"
value: "connection_init"
response:
type: json
value:
type: "connection_ack"
# Subscribe
- match:
type: json
path: "$.type"
value: "subscribe"
response:
type: json
value:
type: "next"
id: "{{message.id}}"
payload:
data:
onMessage:
id: "{{uuid}}"
content: "Subscription active"
# Ping
- match:
type: json
path: "$.type"
value: "ping"
response:
type: json
value:
type: "pong"

mockd provides CLI tools for interacting with WebSocket endpoints.

Start an interactive WebSocket session (REPL mode):

Terminal window
# Basic connection
mockd websocket connect ws://localhost:4280/ws
# With custom headers
mockd websocket connect -H "Authorization:Bearer token" ws://localhost:4280/ws
# With subprotocol
mockd websocket connect --subprotocol graphql-ws ws://localhost:4280/graphql
# JSON output format
mockd websocket connect --json ws://localhost:4280/ws

Flags:

  • -H, --header - Custom headers (key:value), repeatable
  • --subprotocol - WebSocket subprotocol
  • -t, --timeout - Connection timeout (default: 30s)
  • --json - Output messages in JSON format

Send a single message and exit:

Terminal window
# Send text message
mockd websocket send ws://localhost:4280/ws "hello"
# Send JSON message
mockd websocket send ws://localhost:4280/ws '{"action":"ping"}'
# Send from file
mockd websocket send ws://localhost:4280/ws @message.json
# With custom headers
mockd websocket send -H "Authorization:Bearer token" ws://localhost:4280/ws "hello"

Flags:

  • -H, --header - Custom headers (key:value), repeatable
  • --subprotocol - WebSocket subprotocol
  • -t, --timeout - Connection timeout (default: 30s)
  • --json - Output result in JSON format

Stream incoming messages:

Terminal window
# Listen indefinitely
mockd websocket listen ws://localhost:4280/ws
# Listen for 10 messages then exit
mockd websocket listen -n 10 ws://localhost:4280/ws
# JSON output
mockd websocket listen --json ws://localhost:4280/ws
# With headers
mockd websocket listen -H "Authorization:Bearer token" ws://localhost:4280/ws

Flags:

  • -H, --header - Custom headers (key:value), repeatable
  • --subprotocol - WebSocket subprotocol
  • -t, --timeout - Connection timeout (default: 30s)
  • -n, --count - Number of messages to receive (0 = unlimited)
  • --json - Output messages in JSON format

Show WebSocket mock status from the admin API:

Terminal window
# Default admin URL
mockd websocket status
# Custom admin URL
mockd websocket status --admin-url http://localhost:9091
# JSON output
mockd websocket status --json

Flags:

Terminal window
# Start server
mockd serve --config mockd.yaml &
# Test echo mode
mockd websocket send ws://localhost:4280/ws/echo "test message"
# Interactive testing
mockd websocket connect ws://localhost:4280/ws/chat
> {"type": "join", "username": "testuser"}
< {"type": "joined", "message": "Welcome to the chat room!", "timestamp": "..."}
> help
< Available commands: ...
Terminal window
# Install wscat
npm install -g wscat
# Connect and interact
wscat -c ws://localhost:4280/ws/chat
> ping
< pong
> {"type": "join", "username": "alice"}
< {"type": "joined", "message": "Welcome to the chat room!", "timestamp": "..."}
Terminal window
# Verify WebSocket endpoint exists (returns 400 for non-upgrade requests)
curl -i http://localhost:4280/ws
# Test with upgrade headers
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
http://localhost:4280/ws
Terminal window
# Install websocat
cargo install websocat
# Simple connection
websocat ws://localhost:4280/ws
# One-shot message
echo "ping" | websocat ws://localhost:4280/ws
# With subprotocol
websocat --protocol chat ws://localhost:4280/ws/chat
const WebSocket = require('ws');
describe('WebSocket Mock', () => {
let ws;
beforeEach((done) => {
ws = new WebSocket('ws://localhost:4280/ws/chat');
ws.on('open', done);
});
afterEach(() => {
ws.close();
});
test('responds to ping with pong', (done) => {
ws.on('message', (data) => {
expect(data.toString()).toBe('pong');
done();
});
ws.send('ping');
});
test('handles JSON messages', (done) => {
ws.on('message', (data) => {
const response = JSON.parse(data.toString());
expect(response.type).toBe('joined');
expect(response.message).toContain('Welcome');
done();
});
ws.send(JSON.stringify({ type: 'join', username: 'testuser' }));
});
});
package main
import (
"testing"
"github.com/gorilla/websocket"
)
func TestWebSocketMock(t *testing.T) {
conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:4280/ws/chat", nil)
if err != nil {
t.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Test ping/pong
if err := conn.WriteMessage(websocket.TextMessage, []byte("ping")); err != nil {
t.Fatalf("Failed to send: %v", err)
}
_, msg, err := conn.ReadMessage()
if err != nil {
t.Fatalf("Failed to read: %v", err)
}
if string(msg) != "pong" {
t.Errorf("Expected 'pong', got '%s'", msg)
}
}
import pytest
import websocket
import json
def test_websocket_ping():
ws = websocket.create_connection("ws://localhost:4280/ws/chat")
ws.send("ping")
result = ws.recv()
assert result == "pong"
ws.close()
def test_websocket_json():
ws = websocket.create_connection("ws://localhost:4280/ws/chat")
ws.send(json.dumps({"type": "join", "username": "testuser"}))
result = json.loads(ws.recv())
assert result["type"] == "joined"
assert "Welcome" in result["message"]
ws.close()