This MQTT testing checklist walks you through the twelve checks that catch most production incidents in MQTT applications before your users do. Each item comes with a quick test you can run from a laptop against a public broker, so by the end you will have a repeatable go-live routine instead of a vague feeling that "it worked on my desk".
Why MQTT Applications Need Their Own Test Plan
Unit tests cover your parsing and business logic, but most MQTT failures happen in the space between the client and the broker: a device that never resubscribes after a reconnect, a dashboard that shows a stale retained value, a Last Will that fires for the wrong reason, or a payload that is suddenly 70 KB because someone added a debug field. None of these show up in a happy-path demo.
The good news is that MQTT behavior is observable. Every message passes through the broker, so with two terminals and a subscriber on a wildcard you can see exactly what your devices and services do. If you need a refresher on the moving parts, start with how MQTT works and come back here.
Set Up a Test Environment First
Use a disposable environment for these checks. The public MQTT test broker at broker.connectmq.com accepts anonymous connections with any non-empty, unique client ID. Pick a random topic prefix so your tests do not collide with anyone else, and remember the public area is readable by everyone, so never send real credentials or customer data.
# Pick a unique prefix for this test session
export PREFIX="connectmq/demo/$(openssl rand -hex 4)"
echo $PREFIX
# Terminal 1: watch everything under your prefix, verbose, MQTT 5
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
-i watcher-$RANDOM -t "$PREFIX/#" -v
Keep that watcher open for the whole session.
Know the public broker limits before you start, because some checks deliberately hit them: 64 KB per message, 10 messages per second per client, 10 subscriptions per client, 8 topic levels and a maximum session expiry of 30 minutes. Your production broker will have different limits, which is exactly why you should test them explicitly.
The MQTT Testing Checklist
Work through the list in order. Items 1 to 4 are about connectivity, 5 to 8 about message semantics, and 9 to 12 about robustness.
- Connection and authentication. Wrong password, wrong port and TLS failures produce clear errors.
- Client ID uniqueness. Two clients with the same ID do not silently kick each other in a loop.
- Reconnect and resubscribe. After a network drop the client reconnects with backoff and restores its subscriptions.
- Keep alive. A half-open connection is detected within roughly 1.5 times the keep alive interval.
- QoS behavior. The QoS you request is the QoS you get, and duplicates are handled.
- Retained state. New subscribers receive the right last-known value, and stale retained messages can be cleared.
- Last Will and Testament. An ungraceful disconnect produces an offline status; a clean shutdown does not.
- Persistent sessions. Messages published while a subscriber is offline arrive when it returns (QoS 1 and 2 only).
- Payload validation. Malformed JSON, missing fields and wrong types are rejected without crashing the consumer.
- Size, rate and topic limits. Oversized payloads, publish bursts and deep topics fail in a predictable way.
- Topic and wildcard design. Subscriptions match exactly what you intend and nothing more.
- Load and soak. The system survives realistic connection counts and message rates for hours, not seconds.
The sections below show how to run each group.
Connectivity Checks (Items 1 to 4)
Start with the negative cases. A good client logs a meaningful reason when it cannot connect; a bad one retries silently forever.
# Plain TCP should succeed
mosquitto_pub -h broker.connectmq.com -p 1883 -i test-pub-$RANDOM \
-t "$PREFIX/ping" -m "hello"
# TLS on 8883 should succeed and validate the Let's Encrypt certificate
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
-i test-tls-$RANDOM -t "$PREFIX/ping" -m "hello over tls"
# Plain MQTT against the TLS port should fail fast, not hang
mosquitto_pub -h broker.connectmq.com -p 8883 -i test-bad-$RANDOM \
-t "$PREFIX/ping" -m "should fail"
For item 2, start two subscribers with the same client ID. The broker disconnects the older session when the newer one connects. If both clients auto-reconnect, they will take turns kicking each other off, which shows up as a rapid connect and disconnect pattern in your logs. Generate client IDs from a stable device identifier plus a role, never from a hard-coded constant shared across a fleet.
For item 3, the most reliable test is physical: run your client, then pull the network cable or disable Wi-Fi for 60 seconds. When the link returns, publish a message from the watcher terminal and confirm your client still receives it. Many libraries reconnect automatically but only restore subscriptions if you subscribe inside the on-connect callback. The Python script below shows the pattern and prints every state change so you can see what happens.
import time
import uuid
import paho.mqtt.client as mqtt
PREFIX = "connectmq/demo/replace-me"
client_id = f"reconnect-test-{uuid.uuid4().hex[:8]}"
def on_connect(client, userdata, flags, reason_code, properties):
print(f"{time.strftime('%X')} connected: {reason_code}")
# Subscribing here means subscriptions come back after every reconnect
client.subscribe(f"{PREFIX}/cmd/#", qos=1)
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"{time.strftime('%X')} disconnected: {reason_code}")
def on_message(client, userdata, msg):
print(f"{time.strftime('%X')} {msg.topic} qos={msg.qos} retain={msg.retain} {msg.payload!r}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id,
protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.reconnect_delay_set(min_delay=1, max_delay=30)
client.connect("broker.connectmq.com", 1883, keepalive=20)
client.loop_forever(retry_first_connection=True)
With a keep alive of 20 seconds, the broker should treat the client as gone roughly 30 seconds after traffic stops. That is item 4: if your device takes several minutes to notice a dead link, your keep alive is too long or the library is not sending PINGREQ.
Message Semantics Checks (Items 5 to 8)
QoS is negotiated per subscription and per publish, and the effective delivery level is the lower of the two. Test it explicitly rather than assuming. If you are unsure what each level guarantees, read MQTT QoS levels explained first.
# Subscribe at QoS 1, publish at QoS 2: the delivery is downgraded to QoS 1
mosquitto_sub -h broker.connectmq.com -i qos-sub-$RANDOM -q 1 \
-t "$PREFIX/qos" -v -d
mosquitto_pub -h broker.connectmq.com -i qos-pub-$RANDOM -q 2 \
-t "$PREFIX/qos" -m "qos test" -d
The -d flag prints the packet flow (PUBLISH, PUBACK, PUBREC and so on), which makes the downgrade visible. Your consumer code should also tolerate duplicates at QoS 1: process the same message twice and confirm nothing breaks, for example by keying writes on a message ID inside the payload.
For retained state, publish a retained value, then connect a brand-new subscriber and confirm it receives it immediately with the retain flag set. Then clear it by publishing an empty retained payload and confirm the next new subscriber gets nothing.
mosquitto_pub -h broker.connectmq.com -i ret-$RANDOM -r \
-t "$PREFIX/sensor1/state" -m '{"temp":21.4}'
mosquitto_sub -h broker.connectmq.com -i ret-check-$RANDOM \
-t "$PREFIX/sensor1/state" -v -C 1 -W 5
# Clear the retained message
mosquitto_pub -h broker.connectmq.com -i ret-clear-$RANDOM -r -n \
-t "$PREFIX/sensor1/state"
The Last Will check needs a client that dies ungracefully. Connect with a will message, then kill the process with kill -9 or pull the network. The watcher should see the will after the keep alive expires. Repeat with a clean Ctrl+C exit that sends DISCONNECT: the will must not be published. The Last Will and Testament guide explains the retained online/offline pattern most fleets use.
mosquitto_sub -h broker.connectmq.com -i lwt-dev-$RANDOM -k 10 \
--will-topic "$PREFIX/dev1/status" --will-payload offline --will-retain \
-t "$PREFIX/dev1/cmd"
For persistent sessions, connect a subscriber with a fixed client ID, QoS 1 and a session expiry, disconnect it, publish three QoS 1 messages, then reconnect with the same ID. All three should arrive. On the public broker keep the session expiry at or under 1,800 seconds.
mosquitto_sub -h broker.connectmq.com -V mqttv5 -i persist-fixed-001 \
-c -x 600 -q 1 -t "$PREFIX/queue" -v
Robustness Checks (Items 9 to 12)
Payload validation belongs in every consumer. Publish garbage on purpose: invalid JSON, a number as a string, an empty payload, a 10 KB string in a field you expect to be short. The consumer should log and skip, not crash and not write nonsense to the database.
for p in '{"temp":' '{"temp":"hot"}' '' '{"unexpected":true}'; do
mosquitto_pub -h broker.connectmq.com -i bad-$RANDOM \
-t "$PREFIX/sensor1/telemetry" -m "$p"
done
Limits are where public and production environments differ most, so test the behavior, not just the number. On the public broker, a payload over 64 KB, more than 10 messages per second from one client, an eleventh subscription or a ninth topic level should all be refused. Make sure your client surfaces that as an error and backs off rather than hammering the broker. On MQTT 5, look for reason codes such as 0x97 (quota exceeded) in the disconnect or acknowledgment. Size limits on a managed MQTT broker depend on the plan; on ConnectMQ, for example, the free Serverless plan allows 256 KB messages and the paid Serverless plan 1 MB.
# 70 KB payload: should be rejected on the public broker
head -c 71680 /dev/urandom | base64 > /tmp/big.txt
mosquitto_pub -h broker.connectmq.com -V mqttv5 -i big-$RANDOM \
-t "$PREFIX/big" -f /tmp/big.txt -d
For wildcard design, subscribe with the exact filter your service uses and publish to topics that should and should not match. A filter like +/+/telemetry often matches more than intended.
Load and soak testing is the last step. Simulate the real number of devices with realistic intervals and payload sizes, and run it for several hours. Do this against a private broker rather than the public one, since the public per-client rate limit is deliberately low. The MQTT load testing tools roundup compares options.
Quick Reference Table
| Check | Pass criteria | Typical failure |
|---|---|---|
| Auth and TLS | Clear error on bad credentials or port | Silent infinite retry |
| Client ID | One session per device | Two devices kicking each other |
| Reconnect | Subscriptions restored automatically | Connected but deaf client |
| Keep alive | Dead link detected in about 1.5x interval | Device offline for minutes unnoticed |
| QoS | Effective QoS as designed, duplicates harmless | Double-counted events |
| Retained | New subscriber sees last value; clearable | Stale value forever |
| Last Will | Fires on crash only | Fires on clean shutdown or never |
| Sessions | Offline QoS 1 messages delivered | Clean session by accident |
| Payloads | Bad input skipped and logged | Consumer crash |
| Limits | Predictable rejection and backoff | Retry storm |
| Wildcards | Only intended topics matched | Data leak across tenants or sites |
| Load | Stable memory and latency for hours | Slow leak after day two |
Tools That Make Testing Faster
Command-line clients are perfect for scripts, but a visual tool speeds up exploratory testing. ConnectMQ ships an in-browser MQTT client and a live topic explorer in the portal, plus a Desktop MQTT explorer and a VS Code extension, so you can watch topics while your test scripts run. Whatever you use, save your test commands in the repository so the next release runs the same checklist. If you are new to the protocol overall, the MQTT tutorial is the best place to build the foundation these checks rely on.
Test on a Private ConnectMQ Broker
Once the public-broker checks pass, repeat them on the environment you will actually ship to. ConnectMQ gives you a private broker with TLS on 8883, WebSocket on 8084, per-plan quotas and generated credentials in a few clicks. Create a free Serverless broker at app.connectmq.com, no credit card required, swap in YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID, and run the same checklist against it before go-live.
Frequently Asked Questions
What should an MQTT testing checklist include?
At minimum: authentication and TLS, client ID uniqueness, reconnect with resubscribe, keep alive, QoS, retained messages, Last Will, persistent sessions, payload validation, broker limits, wildcard matching and a multi-hour load test.
Can I test my MQTT application on a public broker?
Yes, for functional checks. Use a unique client ID and topic prefix and never send sensitive data, because public topics are readable by anyone. Run load tests on a private broker.
How do I test MQTT Last Will without unplugging hardware?
Kill the client process with a hard kill (for example kill -9) so it cannot send DISCONNECT. The broker publishes the will after the keep alive period expires.
Why does my client stop receiving messages after a reconnect?
It probably subscribes only once at startup. Move the subscribe calls into the on-connect callback, or use a persistent session, so subscriptions are restored on every reconnect.



