Skip to content
Back to skills

Mqtt Tests

ASecurity

Test MQTT v5.0 with Mosquitto broker in CI + paho-mqtt clients - QoS 0 / 1 / 2 delivery semantics, retained messages, Last Will and Testament (LWT), shared subscriptions ($share/group/topic), $SYS topic introspection. Critical for IoT, embedded, and M2M systems where wire-level guarantees matter. Also carries STOMP-over-WebSocket + AMQP 0-9-1 broker testing (Spring / RabbitMQ frame, ack-mode, and exchange/binding tests via Testcontainers) in references/stomp-amqp.md. Use when a product speaks...

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 12, 2026
testingpythonphpbashspringtestinggitapisecurity

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 4 files and shows the line behind each finding

Scanned October 12, 2026

npx -y skills add testland/qa --skill mqtt-tests --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mqtt Tests?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mqtt Tests
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/testland-mqtt-tests/badge)](https://www.skillsdirectory.com/skills/testland-mqtt-tests)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mqtt-tests
description: "Test MQTT v5.0 with Mosquitto broker in CI + paho-mqtt clients - QoS 0 / 1 / 2 delivery semantics, retained messages, Last Will and Testament (LWT), shared subscriptions ($share/group/topic), $SYS topic introspection. Critical for IoT, embedded, and M2M systems where wire-level guarantees matter. Also carries STOMP-over-WebSocket + AMQP 0-9-1 broker testing (Spring / RabbitMQ frame, ack-mode, and exchange/binding tests via Testcontainers) in references/stomp-amqp.md. Use when a product speaks MQTT, STOMP, or AMQP on the wire and QoS 1 / 2 redelivery, retained-message state, LWT, ack-mode, or broker-topology behavior needs a broker-backed test - including smoke-testing a new broker auth / ACL / persistence config."
metadata:
  keywords: "mqtt, mqtt-v5, iot, mosquitto, paho-mqtt, realtime-protocols"
  version: "1.3.5"
---

# mqtt-tests

This skill covers the v5.0 surfaces tests must exercise: QoS 0 / 1
/ 2 delivery semantics, retained messages, Last Will and Testament
(LWT), shared subscriptions, and `$SYS` topic introspection - all
per the [MQTT v5.0 spec].

## When to use

- IoT / sensor / M2M product where MQTT is the wire protocol.
- Pre-deploy gate: QoS 1 + 2 redelivery semantics correct,
  retained-message + LWT setup right.
- Smoke test new broker config (auth, ACL, persistence).

## How to use

1. Stand up an `eclipse-mosquitto:2` broker as a CI service on port 1883 with a persistence-enabled config (Step 1).
2. Wire a paho-mqtt v5 client with `callback_api_version=VERSION2` (Step 2).
3. Exercise the QoS matrix - assert QoS 1 redelivers buffered messages to a reconnecting `clean_start=False` session (Step 3).
4. Verify a retained message reaches a late subscriber, then clear it with an empty retained payload (Step 4).
5. Cover the advanced broker behaviors - LWT on abnormal disconnect, shared-subscription round-robin, and `$SYS` diagnostics ([references/lwt-shared-subs-sys.md](references/lwt-shared-subs-sys.md)).
6. Use a unique `client_id` per test and clean up retained state between runs (Anti-patterns).

## Step 1 - Run Mosquitto broker in CI

```yaml
# GitHub Actions service
services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports:
      - 1883:1883
    volumes:
      - ./tests/mosquitto.conf:/mosquitto/config/mosquitto.conf
```

`tests/mosquitto.conf`:

```
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest stdout
```

## Step 2 - paho-mqtt client setup (Python)

```bash
pip install paho-mqtt
```

```python
import paho.mqtt.client as mqtt

def test_connect_v5():
    client = mqtt.Client(
        client_id="test-1",
        protocol=mqtt.MQTTv5,
        callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
    )
    client.connect("localhost", 1883)
    client.loop_start()
    # ... exercise ...
    client.disconnect()
    client.loop_stop()
```

## Step 3 - QoS matrix tests

Per the [MQTT v5.0 spec]:

| QoS | Guarantee | Use |
|---|---|---|
| 0 | At most once (best effort, may be lost) | High-frequency sensor where loss is acceptable |
| 1 | At least once (may duplicate) | Most application messaging |
| 2 | Exactly once (slowest, full PUBREC/PUBREL/PUBCOMP handshake) | Billing, payments, anything dedup-required |

```python
def test_qos1_redelivers_after_disconnect():
    received = []
    sub = mqtt.Client(client_id="sub", protocol=mqtt.MQTTv5,
                       clean_start=False, callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
    sub.on_message = lambda c, u, msg: received.append(msg.payload)
    sub.connect("localhost", 1883)
    sub.subscribe("sensors/temp", qos=1)
    sub.loop_start()
    time.sleep(0.5)

    # Disconnect subscriber, publish messages, reconnect
    sub.disconnect()
    sub.loop_stop()

    pub = mqtt.Client(client_id="pub", protocol=mqtt.MQTTv5,
                       callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
    pub.connect("localhost", 1883)
    pub.publish("sensors/temp", "22.5", qos=1).wait_for_publish()
    pub.disconnect()

    # Reconnect; broker delivers buffered QoS 1 messages
    sub.reconnect()
    sub.loop_start()
    time.sleep(2.0)
    sub.disconnect()
    sub.loop_stop()

    assert b"22.5" in received
```

`clean_start=False` is required for the broker to retain offline
session state; without it, QoS 1 messages are lost.

## Step 4 - Retained message test

Per the [MQTT v5.0 spec], "servers store and distribute the most
recent message on a topic to new subscribers, enabling state sharing
without republishing."

```python
def test_retained_message_delivered_to_late_subscriber():
    # Publisher sets retain=True
    pub = mqtt.Client(client_id="pub", protocol=mqtt.MQTTv5,
                       callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
    pub.connect("localhost", 1883)
    pub.publish("device/status", "online", qos=1, retain=True).wait_for_publish()
    pub.disconnect()

    # New subscriber connects 5s later - still receives retained msg
    time.sleep(5)
    received = []
    sub = mqtt.Client(client_id="sub-late", protocol=mqtt.MQTTv5,
                       callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
    sub.on_message = lambda c, u, msg: received.append(msg.payload)
    sub.connect("localhost", 1883)
    sub.subscribe("device/status", qos=1)
    sub.loop_start()
    time.sleep(1)
    sub.disconnect()
    sub.loop_stop()

    assert received == [b"online"]
```

To clear: publish empty payload with `retain=True`.

## LWT, shared subscriptions, and $SYS introspection

See [references/lwt-shared-subs-sys.md](references/lwt-shared-subs-sys.md)
for Last Will and Testament on abnormal disconnect, shared-subscription
round-robin (`$share/<group>/<topic>`), and `$SYS` broker-diagnostics
tests.

## Worked example

A sensor gateway publishes temperature to `sensors/temp` at QoS 1. QA
needs to confirm a subscriber that drops offline still receives messages
published during the outage.

1. Connect a subscriber with `clean_start=False` and subscribe to `sensors/temp` at QoS 1 (Step 3).
2. Disconnect the subscriber to simulate an outage.
3. From a separate publisher, `publish("sensors/temp", "22.5", qos=1).wait_for_publish()`.
4. Reconnect the subscriber with `reconnect()` and pump the loop for ~2s.
5. Assert `b"22.5" in received` - the broker delivered the buffered QoS 1 message on reconnect.

Result: offline-session persistence is verified end to end. Had the test
used `clean_start=True`, the broker would have discarded the session and
the message would have been lost - the exact regression this test guards.

## Anti-patterns

| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Test only QoS 0 ("works on my machine") | QoS 1/2 redelivery bugs ship | Step 3 covers matrix |
| Use `clean_start=True` then expect persistence | Broker discards session; QoS 1 buffer lost | `clean_start=False` (Step 3) |
| Skip LWT test | Stale "online" status persists when clients crash | LWT reference |
| Hardcode same `client_id` across tests | Broker disconnects existing on connect; flake | Unique `client_id` per test |
| Forget retained-message cleanup | Subsequent test runs see stale state | Publish empty retained payload between tests |

## Limitations

- MQTT v3.1.1 vs v5.0 differs on Will Properties + reason codes;
  pin client + broker version per test.
- Mosquitto auth + ACL are not tested here; enable in a separate
  test suite if security requirements demand.
- Some advanced v5 features (request/response, topic alias) require
  newer paho-mqtt; verify minimum version per the [paho-mqtt
  reference](https://eclipse.dev/paho/index.php?page=clients/python/index.php).

## References

- [MQTT v5.0 spec] - QoS, retained, LWT, shared subscriptions, $SYS
- STOMP over WebSocket + AMQP 0-9-1 broker testing:
  [references/stomp-amqp.md](references/stomp-amqp.md)
- `websocket-tests` - alternative
  for browser-side bidirectional
- `webhook-delivery-tester` (qa-notifications) - HTTP
  alternative for at-least-once delivery

[MQTT v5.0 spec]: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html

Files in this skill

  • .tessl-plugin/plugin.json848 B
  • SKILL.md7.9 KB
  • references/lwt-shared-subs-sys.md3.8 KB
  • references/stomp-amqp.md13.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.