HiveMQ Testcontainer Released

Written by Yannick Weber

Category: HiveMQ Testing HiveMQ MQTT Client MQTT Client

Published: April 22, 2020


HiveMQ Testcontainers

Automated integration testing of MQTT applications is a challenging task since it requires you to simulate or set up an MQTT deployment of some kind. When testing MQTT client applications, you need a deployed MQTT broker. Sharing the deployment across multiple tests can be troublesome. Shared deployments can cause unwanted interferences between tests that produce unexpected results and flaky tests. Integration-testing custom HiveMQ extensions can be even more difficult since the extension must be packaged, deployed, and started automatically. While developing extensions such as our Enterprise Security Extension or the HiveMQ Extension for Kafka, we frequently encountered this issue. To eliminate this pain point, we developed the official HiveMQ Testcontaier, whose release we proudly announce today.

The HiveMQ Testcontainer is a Java library that gives you the right tools for automated JUnit 4 and JUnit 5 testing of MQTT client applications and custom HiveMQ extensions. Use the Testcontainer to start up customizable HiveMQ docker containers that are exclusive to each integration test. On top of that, the process of packaging and deploying HiveMQ extensions directly from source can be automated.

See the project on GitHub.

The library is available on Maven Central for JUnit 4 and JUnit 5.

Add to your project

To add the HiveMQ Testcontainer to your project, add these dependencies to your pom.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependency>
    <groupId>com.hivemq</groupId>
    <artifactId>hivemq-testcontainer-junit4</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

Test your MQTT application

Testing your MQTT application can look something like this:

  1. Create a new HiveMQTestContainerRule
  2. Register the Rule with JUnit 4
  3. Connect your MQTT clients to the HiveMQ instance that is running inside the container. Use the port that you retrieve from the rule with the getMqttPort() method.
  4. Assert the expected behavior.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class TestMqttIT {

    @Rule // 2
    HiveMQTestContainerRule rule = new HiveMQTestContainerRule(); // 1
    
    @Test
    void test_mqtt() throws InterruptedException {
        final Mqtt5BlockingClient publisher = Mqtt5Client.builder()
            .serverPort(rule.getMqttPort()) // 3
            .identifier("publisher")
            .buildBlocking();
    
        publisher.connect();
    
        final Mqtt5BlockingClient subscriber = Mqtt5Client.builder()
            .serverPort(rule.getMqttPort()) // 3
            .identifier("subscriber")
            .buildBlocking();
        
        subscriber.connect();
        
        subscriber.subscribeWith().topicFilter("topic/test").send();
        
        publisher.publishWith()
            .topic("topic/test")
            .payload("Hello World!".getBytes()).send();
        
        final Mqtt5Publish receive = subscriber.publishes(MqttGlobalPublishFilter.ALL).receive();
        
        assertNotNull(receive); // 4
        assertEquals("Hello World!", new String(receive.getPayloadAsBytes())); // 4
    }
}

Test your custom HiveMQ extension

To test your custom HiveMQ extension, you need to have the HiveMQ Extension SDK on your classpath. If the SDK is not already there, add it to your pom.xml:

1
2
3
4
5
<dependency>
    <groupId>com.hivemq</groupId>
    <artifactId>hivemq-extension-sdk</artifactId>
    <version>4.3.0</version>
</dependency>

The extension to test provides a custom PublishInboundInterceptor which replaces every incoming publish payload with the string "modified". It looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class MyExtension implements ExtensionMain {

    @Override
    public void extensionStart(@NotNull ExtensionStartInput extensionStartInput, @NotNull ExtensionStartOutput extensionStartOutput) {
    
        final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {
            publishInboundOutput.getPublishPacket().setPayload(ByteBuffer.wrap("modified".getBytes(StandardCharsets.UTF_8)));
        };
        
        final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {
            clientContext.addPublishInboundInterceptor(publishInboundInterceptor);
        };
        
        Services.initializerRegistry().setClientInitializer(clientInitializer);
    }
    
    ...
    
}

To actually test the HiveMQ extension, we do the following:

  1. Create a new HiveMQTestContainerRule
  2. Register the Rule with JUnit 4
  3. Add the HiveMQ Extension to the HiveMQTestContainerRule by providing its id, name, version, priority, start-priority and `main class``
  4. Connect your MQTT clients to the HiveMQ instance that is running inside the container. Use the port that you retrieve from the rule with the getMqttPort() method.
  5. Assert the expected behavior.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TestMqttIT {

    @Rule // 2
    HiveMQTestContainerRule rule = new HiveMQTestContainerRule() // 1
        .withExtension(HiveMQExtension.builder()
            .id("extension-1")
            .name("my-extension")
            .version("1.0")
            .mainClass(MyExtension.class).build()) // 3
    
    @Test
    void test_mqtt() throws InterruptedException {
        final Mqtt5BlockingClient publisher = Mqtt5Client.builder()
            .serverPort(rule.getMqttPort()) // 4
            .identifier("publisher")
            .buildBlocking();
    
        publisher.connect();
    
        final Mqtt5BlockingClient subscriber = Mqtt5Client.builder()
            .serverPort(rule.getMqttPort()) // 4
            .identifier("subscriber")
            .buildBlocking();
        
        subscriber.connect();
        
        subscriber.subscribeWith().topicFilter("topic/test").send();
        
        publisher.publishWith()
            .topic("topic/test")
            .payload("Hello World!".getBytes()).send();
        
        final Mqtt5Publish receive = subscriber.publishes(MqttGlobalPublishFilter.ALL).receive();
        
        assertNotNull(receive); // 5
        assertEquals("modified", new String(receive.getPayloadAsBytes())); // 5
    }
}

Using JUnit5

If you want to use JUnit 5, add these dependencies to your pom.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>com.hivemq</groupId>
    <artifactId>hivemq-testcontainer-junit5</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.6.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.6.1</version>
    <scope>test</scope>
</dependency>

The API is almost identical. Simply replace @Rule with @RegisterExtension and HiveMQTestContainerRule with HiveMQTestContainerExtension:

1
2
@RegisterExtension
HiveMQTestContainerExtension extension = new HiveMQTestContainerExtension()

Conclusion

Using the HiveMQ Testcontainer makes the process of automatic testing of MQTT client applications and custom HiveMQ extensions easier and more robust.

About Yannick Weber

Yannick is a software developer at HiveMQ and maintainer of the HiveMQ Testcontainer.
Contact Yannick

HiveMQ ESE 1.5.1 released
HiveMQ 4.2.3 released