How to Test MQTT Client Applications
Automatic testing of MQTT client applications is a challenging task since an MQTT Broker deployment of some kind is required. If you use a locally installed MQTT Broker for testing, you can not be sure that the tests run with the same results in a different environment. For example, when the tests are run on the machine of a coworker or a continuous integration environment. When you use a public MQTT broker, it is always possible for other clients to interfere with your test. On top of that, you are fully dependent on the uptime of the public MQTT broker: if the broker is unavailable, your tests fail. The solution to this problem is to automatically start an exclusive MQTT broker for each integration test and destroy it afterwards. This solution can be realized with the help of the official HiveMQ Testcontainers Module.
Requirements
MQTT application to test
Docker for running HiveMQ containers
Java libraries
JUnit 4 or JUnit 5 as a testing framework
HiveMQ Testcontainer for starting and stopping HiveMQ containers
Mockito as a mocking framework
HiveMQ MQTT Client to create test data
Example MQTT application
As an example, we can test the implementation of the MQTT client that is embedded inside a SmartAquarium. The implementation has the following functionality that needs to be tested:
Light: can be turned on and off by MQTT messages with the topic ’equipment/light’ and the payload ‘ON’ or ‘OFF’.
CO2 injection: can be turned on and off by MQTT messages with the topic ’equipment/co2’ and the payload ‘ON’ or ‘OFF’.
Pump: can be turned on and off by MQTT messages with the topic ’equipment/pump’ and the payload ‘ON’ or ‘OFF’.
Temperature Sensor: measures the water temperature and publishes it with the topic ‘status/temperature’.
The java implementation of the SmartAquariumClient looks something like this:
The SmartAquariumClient
uses the Eclipse Paho client internally and shows the following behavior:
creates an eclipse paho client with the given broker Uri and client identifier ‘smartaquarium’ (1)
registers itself as Mqtt callback for processing incoming publishes (2)
connects to the broker (3)
subscribes to the topics for Light, CO2, and Pump control (4)
When publishTemperature()
is called the SmartAquariumClient
obtains the temperature from the
TemperatureSensor
(5)publishes the temperature to the topic ‘status/temperature’ (6)
When a publish message is received the SmartAquariumClient
retrieves the payload and converts it into an UTF-8 string (7)
assigns the message to the respective topics of the equipment pieces (8)
decides whether the specific device should be switched off or on (9)
The Light
, CO2
, Pump
and TemperatureSensor
interfaces are passed as constructor parameters, so the SmartAquariumClient
is not concerned with their implementation and object-lifecycle. The client only knows about their interface methods turnOn()
, turnOff()
and getCelsius()
. This neat decoupling induces great testability and allows us to write robust and elegant tests.
Testing the MQTT application
For testing your MQTT client application you add the following dependencies to your build.gradle
:
Test MQTT message processing
In our test we want to ensure that the SmartAquariumClient
interacts with the Pump, CO2 and Light correctly when the respective MQTT messages are received.
Steps for testing:
Register the
HiveMQContainer
. (1)Build and connect the
testClient
using the port obtained from the container. (2)Register a timeout of 2 minutes for the test. (3)
Since
Light
,Co2
,Pump
, andTemperatureSensor
are passed as constructor parameters, they can be mocked and handed toSmartAquariumClient
. (4)Publish the signal ‘ON’ with the topic ’equipment/light’ with the
testClient
. (5)Verify that the
turnedOn()
method of theLight
is called exactly once. To avoid a race condition, a timeout must be set that waits for the interaction for a specific amount of time. (6)
Test MQTT message publishing
In the next test, we want to ensure that the publishing of the temperature is working as expected.
Steps for testing:
Register the
HiveMQContainer
. (1)Build and connect the test client using the port obtained from the container. (2)
Register a timeout of 2 minutes for the test. (3)
Since
Light
,Co2
,Pump
, andTemperatureSensor
are passed as constructor parameters, they can be mocked and handed toSmartAquariumClient
. (4)Subscribe the
testClient
to the topic where the temperature should be published (5)Listen for publishes with the
testClient
(6)Use Mockito to make the
TemperatureSensor.getCelsius()
return ‘13.0f’ (7)Make the
SmartAquariumClient
publish the temperature (8)Receive the message with the
testClient
using a blocking call to avoid a race condition here (9)Assert expected results (10)
Conclusion
The following best practices where identified:
Design your MQTT client application with testing in mind.
Use an explicit timeout in every test so an unresponsive test does not block the entire pipeline.
Think about concurrency to avoid race conditions in your tests.
You can find the code for the example over at GitHub.
HiveMQ Team
The HiveMQ team loves writing about MQTT, Sparkplug, Industrial IoT, protocols, how to deploy our platform, and more. We focus on industries ranging from energy, to transportation and logistics, to automotive manufacturing. Our experts are here to help, contact us with any questions.