MQTT Mocking
MQTT mocking enables testing of IoT devices, sensor networks, and real-time messaging systems without connecting to actual MQTT brokers. mockd provides a full-featured MQTT broker with configurable topics, authentication, QoS levels, and device simulation.
Overview
Section titled “Overview”Use MQTT mocks when you need to:
- Test IoT applications and device communication
- Simulate sensor networks and telemetry data
- Develop smart home or industrial automation systems
- Create reproducible test scenarios for MQTT clients
- Mock message queues and pub/sub patterns
- Test real-time notification systems
Quick Start
Section titled “Quick Start”Create a minimal MQTT broker mock:
version: "1.0"
mocks: - id: mqtt-broker name: Simple MQTT Broker type: mqtt enabled: true mqtt: port: 1883 topics: - topic: sensors/temperature qos: 1 messages: - payload: '{"value": 22.5, "unit": "celsius"}' repeat: true interval: "5s"Start the server and test:
# Start mockdmockd serve --config mockd.yaml
# Subscribe to messages using mockd CLImockd mqtt subscribe sensors/temperature
# Or use mosquitto clientmosquitto_sub -h localhost -p 1883 -t "sensors/#"Configuration
Section titled “Configuration”Full MQTT Spec
Section titled “Full MQTT Spec”version: "1.0"
mocks: - id: mqtt-full-example name: IoT MQTT Broker type: mqtt enabled: true mqtt: # Required: broker port port: 1883
# TLS configuration (optional) tls: enabled: true certFile: ./certs/server.crt keyFile: ./certs/server.key
# Authentication (optional) auth: enabled: true users: - username: device password: secret123 acl: - topic: "sensors/#" access: write - topic: "commands/#" access: read - username: admin password: admin123 acl: - topic: "#" access: readwrite
# Topic configurations topics: - topic: sensors/temperature qos: 1 retain: true messages: - payload: '{"value": 22.5}' interval: "5s" repeat: true
- topic: commands/+/execute qos: 2 onPublish: response: payload: '{"status": "executed"}' forward: responses/device
- topic: devices/{device_id}/telemetry qos: 0 deviceSimulation: enabled: true deviceCount: 10 deviceIdPattern: "device_{index}"Configuration Reference
Section titled “Configuration Reference”| Field | Type | Description |
|---|---|---|
port | integer | MQTT broker port (default: 1883) |
tls | object | TLS/SSL configuration |
auth | object | Authentication settings |
topics | array | Topic configurations |
Topics and Messages
Section titled “Topics and Messages”Topics define how the broker handles message publishing and subscription.
Basic Topic Configuration
Section titled “Basic Topic Configuration”topics: - topic: sensors/temperature qos: 1 retain: true messages: - payload: | { "deviceId": "temp_001", "value": 22.5, "unit": "celsius", "timestamp": "{{ now }}" } interval: "5s" repeat: trueTopic Fields
Section titled “Topic Fields”| Field | Type | Description |
|---|---|---|
topic | string | Topic pattern (supports wildcards) |
qos | integer | Quality of Service level (0, 1, or 2) |
retain | boolean | Retain last message for new subscribers |
messages | array | Predefined messages to publish |
onPublish | object | Handler for received messages |
deviceSimulation | object | Simulate multiple devices |
Topic Wildcards
Section titled “Topic Wildcards”MQTT supports two wildcard characters for topic subscriptions:
# Single-level wildcard (+)# Matches exactly one topic leveltopics: - topic: sensors/+/temperature # matches sensors/room1/temperature # matches sensors/room2/temperature
# Multi-level wildcard (#)# Matches any number of levels (must be last character)topics: - topic: sensors/# # matches sensors/temperature # matches sensors/humidity # matches sensors/room1/tempMessage Configuration
Section titled “Message Configuration”Define messages to publish automatically on a topic:
messages: - payload: '{"temperature": 22.5}' delay: "0s" # Initial delay before first publish repeat: true # Continuously publish at interval interval: "5s" # Time between repeated publishesMessage fields:
| Field | Type | Description |
|---|---|---|
payload | string | Message content (string or JSON) |
delay | duration | Initial delay before first publish |
repeat | boolean | Continuously publish at interval |
interval | duration | Time between repeated publishes |
Templated Payloads
Section titled “Templated Payloads”Use template expressions in message payloads:
messages: - payload: | { "deviceId": "sensor_001", "temperature": {{ random.int(18, 28) }}, "humidity": {{ random.int(40, 80) }}, "timestamp": "{{ now }}" } interval: "5s" repeat: trueAvailable templates:
| Template | Description |
|---|---|
{{ now }} | Current ISO timestamp |
{{ uuid }} | Random UUID |
{{ timestamp }} | Unix timestamp |
{{ random.int(min, max) }} | Random integer in range |
Retained Messages
Section titled “Retained Messages”Retained messages are stored on the broker and delivered to new subscribers immediately:
topics: - topic: devices/status retain: true messages: - payload: '{"status": "online", "uptime": 12345}'When a client subscribes to devices/status, they immediately receive the last retained message.
QoS Levels
Section titled “QoS Levels”MQTT defines three Quality of Service levels for message delivery:
QoS 0 - At Most Once
Section titled “QoS 0 - At Most Once”Fire and forget. No acknowledgment required. Messages may be lost.
topics: - topic: sensors/motion qos: 0 messages: - payload: '{"detected": true}'Use for:
- High-frequency sensor data where occasional loss is acceptable
- Non-critical notifications
- Status updates that will be superseded
QoS 1 - At Least Once
Section titled “QoS 1 - At Least Once”Message delivered at least once. May have duplicates. Requires acknowledgment.
topics: - topic: sensors/temperature qos: 1 messages: - payload: '{"value": 22.5}'Use for:
- Important sensor readings
- Command acknowledgments
- Data that should not be lost
QoS 2 - Exactly Once
Section titled “QoS 2 - Exactly Once”Message delivered exactly once. Highest overhead with four-step handshake.
topics: - topic: commands/critical qos: 2 onPublish: response: payload: '{"status": "executed"}'Use for:
- Financial transactions
- Critical commands
- Messages where duplicates cause problems
Publish Handlers
Section titled “Publish Handlers”Respond to messages received on a topic using publish handlers.
Basic Response
Section titled “Basic Response”topics: - topic: commands/device/+ qos: 1 onPublish: response: payload: '{"status": "acknowledged", "timestamp": "{{ now }}"}' delay: "100ms"Forward Messages
Section titled “Forward Messages”Forward received messages to another topic:
topics: - topic: commands/device/+ qos: 1 onPublish: response: payload: '{"status": "acknowledged"}' forward: responses/deviceHandler Fields
Section titled “Handler Fields”| Field | Type | Description |
|---|---|---|
response | object | Message to publish in response |
response.payload | string | Response message content |
response.delay | duration | Delay before sending response |
forward | string | Topic to forward the message to |
Using Message Content in Responses
Section titled “Using Message Content in Responses”Reference the received message in your response:
topics: - topic: commands/+/execute qos: 1 onPublish: response: payload: | { "status": "executed", "command": "{{ message.payload }}", "executedAt": "{{ now }}" }Authentication
Section titled “Authentication”Configure username/password authentication with Access Control Lists (ACL).
Enable Authentication
Section titled “Enable Authentication”mqtt: port: 1883 auth: enabled: true users: - username: sensor-gateway password: gateway-secret acl: - topic: "sensors/#" access: readwrite - username: dashboard password: readonly-pass acl: - topic: "sensors/#" access: readUser Configuration
Section titled “User Configuration”| Field | Type | Description |
|---|---|---|
username | string | Login username |
password | string | Login password |
acl | array | Access control rules |
ACL Rules
Section titled “ACL Rules”| Field | Type | Description |
|---|---|---|
topic | string | Topic pattern (supports wildcards) |
access | string | Access level |
Access levels:
| Level | Description |
|---|---|
read / subscribe | Subscribe only |
write / publish | Publish only |
readwrite / all | Both subscribe and publish |
Role-Based Access Example
Section titled “Role-Based Access Example”auth: enabled: true users: # IoT devices - can only publish sensor data - username: device password: device123 acl: - topic: "sensors/#" access: write - topic: "commands/#" access: read
# Monitoring dashboard - read-only access - username: monitor password: monitor123 acl: - topic: "#" access: read
# Admin - full access - username: admin password: admin123 acl: - topic: "#" access: allTLS Configuration
Section titled “TLS Configuration”Enable TLS/SSL for encrypted MQTT connections.
Basic TLS Setup
Section titled “Basic TLS Setup”mqtt: port: 8883 tls: enabled: true certFile: ./certs/server.crt keyFile: ./certs/server.keyGenerate Self-Signed Certificates
Section titled “Generate Self-Signed Certificates”# Generate private keyopenssl genrsa -out server.key 2048
# Generate self-signed certificateopenssl req -new -x509 -key server.key -out server.crt -days 365
# Connect with TLSmosquitto_sub -h localhost -p 8883 --cafile server.crt -t "sensors/#"Device Simulation
Section titled “Device Simulation”Simulate multiple IoT devices publishing to topics.
Configuration
Section titled “Configuration”topics: - topic: devices/{device_id}/telemetry qos: 1 deviceSimulation: enabled: true deviceCount: 100 deviceIdPattern: "device_{index}"This creates 100 virtual devices (device_1 through device_100) each publishing to their own topic:
devices/device_1/telemetrydevices/device_2/telemetry- …
devices/device_100/telemetry
Device Simulation Fields
Section titled “Device Simulation Fields”| Field | Type | Description |
|---|---|---|
enabled | boolean | Enable device simulation |
deviceCount | integer | Number of virtual devices |
deviceIdPattern | string | Pattern for device IDs ({n}, {id}, or {index} replaced with number) |
Simulating Device Fleet
Section titled “Simulating Device Fleet”topics: # Temperature sensors - topic: sensors/temperature/{device_id} qos: 1 deviceSimulation: enabled: true deviceCount: 50 deviceIdPattern: "temp_sensor_{index}" messages: - payload: | { "deviceId": "{{deviceId}}", "temperature": {{ random.int(15, 35) }}, "timestamp": "{{ now }}" } interval: "10s" repeat: true
# Motion sensors - topic: sensors/motion/{device_id} qos: 0 deviceSimulation: enabled: true deviceCount: 20 deviceIdPattern: "motion_{index}"Examples
Section titled “Examples”IoT Sensor Network
Section titled “IoT Sensor Network”version: "1.0"
mocks: - id: iot-sensors name: IoT Sensor Network type: mqtt enabled: true mqtt: port: 1883
auth: enabled: true users: - username: sensor password: sensor123 acl: - topic: "sensors/#" access: write - username: gateway password: gateway123 acl: - topic: "#" access: all
topics: # Temperature sensor - topic: sensors/temperature qos: 1 retain: true messages: - payload: | { "deviceId": "temp_001", "type": "temperature", "value": {{ random.int(18, 28) }}, "unit": "celsius", "timestamp": "{{ now }}" } interval: "5s" repeat: true
# Humidity sensor - topic: sensors/humidity qos: 1 retain: true messages: - payload: | { "deviceId": "humid_001", "type": "humidity", "value": {{ random.int(40, 80) }}, "unit": "percent", "timestamp": "{{ now }}" } interval: "5s" repeat: true
# Motion sensor (less frequent) - topic: sensors/motion qos: 0 messages: - payload: | { "deviceId": "motion_001", "type": "motion", "detected": true, "zone": "entrance", "timestamp": "{{ now }}" } interval: "30s" repeat: true
# Device status (retained) - topic: devices/status qos: 1 retain: true messages: - payload: | { "deviceId": "gateway_001", "status": "online", "uptime": {{ random.int(1000, 50000) }}, "firmware": "1.2.3", "timestamp": "{{ now }}" } delay: "1s"Command and Response System
Section titled “Command and Response System”version: "1.0"
mocks: - id: command-system name: Device Command System type: mqtt enabled: true mqtt: port: 1883
topics: # Command topic - listens for commands and responds - topic: commands/device/+ qos: 2 onPublish: response: payload: | { "status": "acknowledged", "command": "{{ message.payload }}", "executedAt": "{{ now }}", "id": "{{ uuid }}" } delay: "50ms" forward: responses/device
# Response topic for monitoring - topic: responses/device qos: 1 retain: true
# Alert topic with high QoS - topic: alerts/+ qos: 2 retain: trueSmart Home System
Section titled “Smart Home System”version: "1.0"
mocks: - id: smart-home name: Smart Home Hub type: mqtt enabled: true mqtt: port: 1883
auth: enabled: true users: - username: homeassistant password: ha-secret acl: - topic: "#" access: all - username: light password: light123 acl: - topic: "home/lights/#" access: readwrite - username: thermostat password: thermo123 acl: - topic: "home/climate/#" access: readwrite
topics: # Living room light - topic: home/lights/living_room/state qos: 1 retain: true messages: - payload: '{"on": true, "brightness": 80, "color": "#ffffff"}'
# Living room light commands - topic: home/lights/living_room/set qos: 1 onPublish: response: payload: | { "on": true, "brightness": 80, "updated": "{{ now }}" } forward: home/lights/living_room/state
# Thermostat - topic: home/climate/thermostat/state qos: 1 retain: true messages: - payload: | { "current_temp": {{ random.int(18, 24) }}, "target_temp": 21, "mode": "heat", "humidity": {{ random.int(40, 60) }} } interval: "30s" repeat: true
# Door sensor - topic: home/security/front_door qos: 1 retain: true messages: - payload: '{"state": "closed", "battery": 95}'
# Motion sensor - topic: home/security/motion/hallway qos: 0 messages: - payload: | { "motion": true, "timestamp": "{{ now }}" } interval: "60s" repeat: trueNotification Service
Section titled “Notification Service”version: "1.0"
mocks: - id: notifications name: Push Notification Service type: mqtt enabled: true mqtt: port: 1883
topics: # User notifications - topic: notifications/user/+ qos: 1 messages: - payload: | { "id": "{{ uuid }}", "type": "info", "title": "System Update", "body": "New features are available", "timestamp": "{{ now }}" } interval: "30s" repeat: true
# Broadcast notifications - topic: notifications/broadcast qos: 1 retain: true messages: - payload: | { "id": "{{ uuid }}", "type": "announcement", "title": "Maintenance Notice", "body": "Scheduled maintenance at midnight", "priority": "high" } delay: "5s"
# Notification acknowledgments - topic: notifications/ack/+ qos: 1 onPublish: response: payload: '{"status": "received", "timestamp": "{{ now }}"}'CLI Commands
Section titled “CLI Commands”mockd provides CLI tools for interacting with MQTT brokers.
mqtt publish
Section titled “mqtt publish”Publish a message to an MQTT topic:
# Publish a simple messagemockd mqtt publish sensors/temperature "25.5"
# Publish with custom brokermockd mqtt publish -b mqtt.example.com:1883 sensors/temp "25.5"
# Publish with authenticationmockd mqtt publish -u user -P pass sensors/temp "25.5"
# Publish with QoS 1 and retainmockd mqtt publish --qos 1 --retain sensors/temp "25.5"
# Publish JSON payloadmockd mqtt publish sensors/data '{"temp": 25.5, "humidity": 60}'
# Publish from filemockd mqtt publish sensors/config @config.jsonFlags:
| Flag | Description |
|---|---|
-b, --broker | MQTT broker address (default: localhost:1883) |
-u, --username | MQTT username |
-P, --password | MQTT password |
--qos | QoS level 0, 1, or 2 (default: 0) |
--retain | Retain message on broker |
mqtt subscribe
Section titled “mqtt subscribe”Subscribe to a topic and print received messages:
# Subscribe to a topicmockd mqtt subscribe sensors/temperature
# Subscribe with wildcardmockd mqtt subscribe "sensors/#"
# Subscribe to single-level wildcardmockd mqtt subscribe "sensors/+/temperature"
# Receive only 5 messages then exitmockd mqtt subscribe -n 5 sensors/temperature
# Subscribe with timeoutmockd mqtt subscribe -t 30s sensors/temperature
# Subscribe with authenticationmockd mqtt subscribe -u user -P pass sensors/temperature
# Subscribe with QoS 1mockd mqtt subscribe --qos 1 sensors/temperatureFlags:
| Flag | Description |
|---|---|
-b, --broker | MQTT broker address (default: localhost:1883) |
-u, --username | MQTT username |
-P, --password | MQTT password |
--qos | QoS level 0, 1, or 2 (default: 0) |
-n, --count | Number of messages to receive (0 = unlimited) |
-t, --timeout | Timeout duration (e.g., 30s, 5m) |
mqtt status
Section titled “mqtt status”Show MQTT broker status from the admin API:
# Default admin URLmockd mqtt status
# Custom admin URLmockd mqtt status --admin-url http://localhost:9091
# JSON outputmockd mqtt status --jsonFlags:
| Flag | Description |
|---|---|
--admin-url | Admin API base URL (default: http://localhost:4290) |
--json | Output in JSON format |
Testing
Section titled “Testing”Using mockd CLI
Section titled “Using mockd CLI”# Start servermockd serve --config mockd.yaml &
# Subscribe to all sensors in backgroundmockd mqtt subscribe "sensors/#" &
# Publish test messagesmockd mqtt publish sensors/temperature '{"value": 25.5}'mockd mqtt publish sensors/humidity '{"value": 65}'
# Test command/responsemockd mqtt subscribe responses/device &mockd mqtt publish commands/device/001 '{"action": "restart"}'Using Mosquitto Clients
Section titled “Using Mosquitto Clients”Install mosquitto clients:
# Ubuntu/Debianapt install mosquitto-clients
# macOSbrew install mosquitto
# Subscribe to all sensorsmosquitto_sub -h localhost -p 1883 -t "sensors/#" -v
# Subscribe to specific topicmosquitto_sub -h localhost -p 1883 -t "sensors/temperature"
# Publish a messagemosquitto_pub -h localhost -p 1883 -t "sensors/temperature" -m '{"value": 25.5}'
# Publish with QoS 1mosquitto_pub -h localhost -p 1883 -t "sensors/temperature" -m '{"value": 25.5}' -q 1
# Subscribe with authenticationmosquitto_sub -h localhost -p 1883 -u device -P secret123 -t "sensors/#"
# Publish with retain flagmosquitto_pub -h localhost -p 1883 -t "status" -m "online" -rIntegration Tests (JavaScript)
Section titled “Integration Tests (JavaScript)”const mqtt = require('mqtt');
describe('MQTT Mock', () => { let client;
beforeEach((done) => { client = mqtt.connect('mqtt://localhost:1883'); client.on('connect', done); });
afterEach(() => { client.end(); });
test('receives temperature readings', (done) => { client.subscribe('sensors/temperature', (err) => { expect(err).toBeNull(); });
client.on('message', (topic, message) => { const data = JSON.parse(message.toString()); expect(topic).toBe('sensors/temperature'); expect(data).toHaveProperty('value'); expect(data).toHaveProperty('unit'); done(); }); });
test('publishes and receives message', (done) => { const testTopic = 'test/messages'; const testMessage = { id: 1, text: 'hello' };
client.subscribe(testTopic);
client.on('message', (topic, message) => { const data = JSON.parse(message.toString()); expect(data).toEqual(testMessage); done(); });
client.publish(testTopic, JSON.stringify(testMessage)); });});Integration Tests (Go)
Section titled “Integration Tests (Go)”package main
import ( "encoding/json" "testing" "time"
mqtt "github.com/eclipse/paho.mqtt.golang")
func TestMQTTMock(t *testing.T) { opts := mqtt.NewClientOptions(). AddBroker("tcp://localhost:1883"). SetClientID("test-client")
client := mqtt.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { t.Fatalf("Failed to connect: %v", token.Error()) } defer client.Disconnect(250)
// Subscribe and receive message received := make(chan []byte, 1) client.Subscribe("sensors/temperature", 1, func(c mqtt.Client, m mqtt.Message) { received <- m.Payload() })
select { case msg := <-received: var data map[string]interface{} if err := json.Unmarshal(msg, &data); err != nil { t.Fatalf("Failed to parse message: %v", err) } if _, ok := data["value"]; !ok { t.Error("Expected 'value' field in message") } case <-time.After(10 * time.Second): t.Fatal("Timeout waiting for message") }}Integration Tests (Python)
Section titled “Integration Tests (Python)”import pytestimport paho.mqtt.client as mqttimport jsonimport time
def test_mqtt_subscribe(): received = []
def on_message(client, userdata, msg): received.append(json.loads(msg.payload))
client = mqtt.Client() client.on_message = on_message client.connect("localhost", 1883) client.subscribe("sensors/temperature") client.loop_start()
# Wait for message time.sleep(6) client.loop_stop() client.disconnect()
assert len(received) > 0 assert "value" in received[0]
def test_mqtt_publish(): received = []
def on_message(client, userdata, msg): received.append(json.loads(msg.payload))
client = mqtt.Client() client.on_message = on_message client.connect("localhost", 1883) client.subscribe("test/topic") client.loop_start()
# Publish message client.publish("test/topic", json.dumps({"test": True})) time.sleep(1)
client.loop_stop() client.disconnect()
assert len(received) == 1 assert received[0]["test"] == TrueTesting with Authentication
Section titled “Testing with Authentication”# Start server with auth enabledmockd serve --config mockd-auth.yaml &
# Test valid credentialsmosquitto_sub -h localhost -p 1883 -u sensor -P sensor123 -t "sensors/#"
# Test invalid credentials (should fail)mosquitto_sub -h localhost -p 1883 -u invalid -P wrong -t "sensors/#"
# Test ACL (device user can only write to sensors)mosquitto_pub -h localhost -p 1883 -u device -P device123 -t "sensors/temp" -m "25"mosquitto_sub -h localhost -p 1883 -u device -P device123 -t "commands/#" # deniedTesting QoS Levels
Section titled “Testing QoS Levels”# QoS 0 - At most oncemosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos0" -q 0
# QoS 1 - At least oncemosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos1" -q 1
# QoS 2 - Exactly oncemosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos2" -q 2
# Subscribe with specific QoSmosquitto_sub -h localhost -p 1883 -t "qos/test" -q 2Next Steps
Section titled “Next Steps”- Response Templating - Dynamic response values
- WebSocket Mocking - Real-time bidirectional communication
- TLS/HTTPS - Secure connections