Testing reactive Java microservices

duration 20 minutes

Prerequisites:

Learn how to test reactive Java microservices in true-to-production environments using MicroShed Testing.

What you’ll learn

You will learn how to write integration tests for reactive Java microservices and to run the tests in true-to-production environments by using containers with MicroShed Testing. MicroShed Testing tests your containerized application from outside the container so that you are testing the exact same image that runs in production. The reactive application in this guide sends and receives messages between services by using an external message broker, Apache Kafka. Using an external message broker enables asynchronous communications between services so that requests are non-blocking and decoupled from responses. You can learn more about reactive Java services that use an external message broker to manage communications in the Creating reactive Java microservices guide.

Reactive system inventory application

True-to-production integration testing with MicroShed Testing

Tests sometimes pass during the development and testing stages of an application’s lifecycle but then fail in production because of differences between your development and production environments. While you can create mock objects and custom setups to minimize differences between environments, it is difficult to mimic a production system for an application that uses an external messaging system. MicroShed Testing addresses this problem by enabling the testing of applications in the same Docker containers that you’ll use in production. As a result, your environment remains the same throughout the application’s lifecycle – from development, through testing, and into production. You can learn more about MicroShed Testing in the Testing a MicroProfile or Jakarta EE application guide.

Additional prerequisites

You need to have Docker installed. For installation instructions, refer to the official Docker documentation. You will build and run the microservices in Docker containers. An installation of Apache Kafka is provided in another Docker container.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-reactive-service-testing.git
cd guide-reactive-service-testing

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the tests, go to the finish directory and run the following Maven goal to install the models artifact to the local Maven repository:

cd finish
mvn -pl models install

Run the following command to download or update to the latest Open Liberty Docker image:

docker pull icr.io/appcafe/open-liberty:full-java11-openj9-ubi

Next, navigate to the finish/system directory and run the following Maven goal to build the system service and run the integration tests on an Open Liberty server in a container:

cd system
mvn verify

You will see the following output:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 33.001 s - in it.io.openliberty.guides.system.SystemServiceIT

 Results:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0


 --- maven-failsafe-plugin:2.22.2:verify (verify) @ system ---
 ------------------------------------------------------------------------
 BUILD SUCCESS
 ------------------------------------------------------------------------
 Total time:  52.817 s
 Finished at: 2020-03-13T16:28:55-04:00
 ------------------------------------------------------------------------

This command might take some time to run the first time because the dependencies and the Docker image for Open Liberty must download. If you run the same command again, it will be faster.

You can also try out the inventory integration tests by repeating the same commands in the finish/inventory directory.

Testing with the Kafka consumer client

system/pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0"
  3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5    <modelVersion>4.0.0</modelVersion>
  6
  7    <groupId>io.openliberty.guides</groupId>
  8    <artifactId>system</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>1.8</maven.compiler.source>
 14        <maven.compiler.target>1.8</maven.compiler.target>
 15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 16        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 17        <!-- Liberty configuration -->
 18        <liberty.var.default.http.port>9083</liberty.var.default.http.port>
 19        <liberty.var.default.https.port>9446</liberty.var.default.https.port>
 20    </properties>
 21
 22    <dependencies>
 23        <!-- Provided dependencies -->
 24        <dependency>
 25            <groupId>jakarta.platform</groupId>
 26            <artifactId>jakarta.jakartaee-api</artifactId>
 27            <version>8.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>3.3</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        <dependency>
 38            <groupId>org.eclipse.microprofile.reactive.messaging</groupId>
 39            <artifactId>microprofile-reactive-messaging-api</artifactId>
 40            <version>1.0</version>
 41            <scope>provided</scope>
 42        </dependency>
 43
 44        <!-- Required dependencies -->
 45        <dependency>
 46            <groupId>io.openliberty.guides</groupId>
 47            <artifactId>models</artifactId>
 48            <version>1.0-SNAPSHOT</version>
 49        </dependency>
 50        <dependency>
 51            <groupId>org.apache.kafka</groupId>
 52            <artifactId>kafka-clients</artifactId>
 53            <version>2.8.1</version>
 54        </dependency>
 55        <dependency>
 56            <groupId>io.reactivex.rxjava3</groupId>
 57            <artifactId>rxjava</artifactId>
 58            <version>3.1.2</version>
 59        </dependency>
 60
 61        <!-- For tests -->
 62        <!-- tag::dependencies[] -->
 63        <dependency>
 64            <groupId>org.microshed</groupId>
 65            <artifactId>microshed-testing-liberty</artifactId>
 66            <version>0.9.1</version>
 67            <scope>test</scope>
 68        </dependency>
 69        <dependency>
 70            <groupId>org.testcontainers</groupId>
 71            <artifactId>kafka</artifactId>
 72            <version>1.16.2</version>
 73            <scope>test</scope>
 74        </dependency>
 75        <!-- end::dependencies[] -->
 76        <dependency>
 77            <groupId>org.junit.jupiter</groupId>
 78            <artifactId>junit-jupiter</artifactId>
 79            <version>5.7.0</version>
 80            <scope>test</scope>
 81        </dependency>
 82    </dependencies>
 83
 84    <build>
 85        <finalName>${project.artifactId}</finalName>
 86        <plugins>
 87            <plugin>
 88                <groupId>org.apache.maven.plugins</groupId>
 89                <artifactId>maven-war-plugin</artifactId>
 90                <version>3.3.2</version>
 91                <configuration>
 92                    <packagingExcludes>pom.xml</packagingExcludes>
 93                </configuration>
 94            </plugin>
 95
 96            <!-- Liberty plugin -->
 97            <plugin>
 98                <groupId>io.openliberty.tools</groupId>
 99                <artifactId>liberty-maven-plugin</artifactId>
100                <version>3.7.1</version>
101            </plugin>
102
103            <!-- Plugin to run unit tests -->
104            <plugin>
105                <groupId>org.apache.maven.plugins</groupId>
106                <artifactId>maven-surefire-plugin</artifactId>
107                <version>2.22.2</version>
108            </plugin>
109
110            <!-- Plugin to run integration tests -->
111            <plugin>
112                <groupId>org.apache.maven.plugins</groupId>
113                <artifactId>maven-failsafe-plugin</artifactId>
114                <version>2.22.2</version>
115                <executions>
116                    <execution>
117                        <id>integration-test</id>
118                        <goals>
119                            <goal>integration-test</goal>
120                        </goals>
121                        <configuration>
122                            <trimStackTrace>false</trimStackTrace>
123                        </configuration>
124                    </execution>
125                    <execution>
126                        <id>verify</id>
127                        <goals>
128                            <goal>verify</goal>
129                        </goals>
130                    </execution>
131                </executions>
132            </plugin>
133        </plugins>
134    </build>
135</project>

inventory/pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3    <modelVersion>4.0.0</modelVersion>
  4
  5    <groupId>io.openliberty.guides</groupId>
  6    <artifactId>inventory</artifactId>
  7    <version>1.0-SNAPSHOT</version>
  8    <packaging>war</packaging>
  9
 10    <properties>
 11        <maven.compiler.source>1.8</maven.compiler.source>
 12        <maven.compiler.target>1.8</maven.compiler.target>
 13        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 14        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 15        <!-- Liberty configuration -->
 16        <liberty.var.default.http.port>9085</liberty.var.default.http.port>
 17        <liberty.var.default.https.port>9448</liberty.var.default.https.port>
 18    </properties>
 19    
 20    <dependencies>
 21        <!-- Provided dependencies -->
 22        <dependency>
 23            <groupId>jakarta.platform</groupId>
 24            <artifactId>jakarta.jakartaee-api</artifactId>
 25            <version>8.0.0</version>
 26            <scope>provided</scope>
 27        </dependency>
 28        <dependency>
 29            <groupId>javax.enterprise.concurrent</groupId>
 30            <artifactId>javax.enterprise.concurrent-api</artifactId>
 31            <version>1.1</version>
 32            <scope>provided</scope>
 33        </dependency>
 34        <dependency>
 35            <groupId>org.eclipse.microprofile</groupId>
 36            <artifactId>microprofile</artifactId>
 37            <version>3.3</version>
 38            <type>pom</type>
 39            <scope>provided</scope>
 40        </dependency>
 41        <dependency>
 42            <groupId>org.eclipse.microprofile.reactive.messaging</groupId>
 43            <artifactId>microprofile-reactive-messaging-api</artifactId>
 44            <version>1.0</version>
 45            <scope>provided</scope>
 46        </dependency>
 47        
 48        <!--  Required dependencies -->
 49        <dependency>
 50           <groupId>io.openliberty.guides</groupId>
 51           <artifactId>models</artifactId>
 52           <version>1.0-SNAPSHOT</version>
 53        </dependency>
 54        <dependency>
 55            <groupId>org.apache.kafka</groupId>
 56            <artifactId>kafka-clients</artifactId>
 57            <version>2.8.1</version>
 58        </dependency>
 59
 60        <!-- For tests -->
 61        <!-- tag::dependencies[] -->
 62        <dependency>
 63            <groupId>org.microshed</groupId>
 64            <artifactId>microshed-testing-liberty</artifactId>
 65            <version>0.9.1</version>
 66            <scope>test</scope>
 67        </dependency>
 68        <dependency>
 69            <groupId>org.testcontainers</groupId>
 70            <artifactId>kafka</artifactId>
 71            <version>1.16.2</version>
 72            <scope>test</scope>
 73        </dependency>
 74        <!-- end::dependencies[] -->
 75        <dependency>
 76            <groupId>org.junit.jupiter</groupId>
 77            <artifactId>junit-jupiter</artifactId>
 78            <version>5.7.0</version>
 79            <scope>test</scope>
 80        </dependency>
 81    </dependencies>
 82
 83    <build>
 84        <finalName>${project.artifactId}</finalName>
 85        <plugins>
 86            <plugin>
 87                <groupId>org.apache.maven.plugins</groupId>
 88                <artifactId>maven-war-plugin</artifactId>
 89                <version>3.3.2</version>
 90                <configuration>
 91                    <packagingExcludes>pom.xml</packagingExcludes>
 92                </configuration>
 93            </plugin>
 94
 95            <!-- Liberty plugin -->
 96            <plugin>
 97                <groupId>io.openliberty.tools</groupId>
 98                <artifactId>liberty-maven-plugin</artifactId>
 99                <version>3.7.1</version>
100            </plugin>
101
102            <!-- Plugin to run unit tests -->
103            <plugin>
104                <groupId>org.apache.maven.plugins</groupId>
105                <artifactId>maven-surefire-plugin</artifactId>
106                <version>2.22.2</version>
107            </plugin>
108
109            <!-- Plugin to run integration tests -->
110            <plugin>
111                <groupId>org.apache.maven.plugins</groupId>
112                <artifactId>maven-failsafe-plugin</artifactId>
113                <version>2.22.2</version>
114                <executions>
115                    <execution>
116                        <goals>
117                            <goal>integration-test</goal>
118                            <goal>verify</goal>
119                        </goals>
120                    </execution>
121                </executions>
122            </plugin>
123        </plugins>
124    </build>
125</project>

Navigate to the start directory to begin.

The example reactive application consists of the system and inventory microservices. The system microservice produces messages to the Kafka message broker, and the inventory microservice consumes messages from the Kafka message broker. You will write integration tests to see how you can use the Kafka consumer and producer client APIs to test each service. MicroShed Testing and Kafka Testcontainers have already been included as required test dependencies in your Maven pom.xml files for the system and inventory services.

The start directory contains three directories: the system service directory, the inventory service directory, and the models directory. The models directory contains the model class that defines the structure of the system load data that is used in the application. Run the following Maven goal to install the packaged models artifact to the local Maven repository so it can be used later by the system and inventory services:

mvn -pl models install

If you don’t have the latest Docker image, pull it by running the following command:

docker pull icr.io/appcafe/open-liberty:full-java11-openj9-ubi

With Open Liberty development mode, known as dev mode, you can use MicroShed Testing to run tests on an already running Open Liberty server. Navigate to the start/system directory.

When you run Open Liberty in development mode, known as dev mode, the server listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your application server in dev mode is ready:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

Now you can add your test files.

The running system service searches for a Kafka topic to push its messages to. Because there are not yet any running Kafka services, the system service throws errors. Later in the guide, you will write and run tests that start a Kafka Testcontainer that can communicate with the system service. This will resolve the errors that you see now.

Configuring your containers

Create a class to externalize your container configurations.

Create the AppContainerConfig class.
system/src/test/java/it/io/openliberty/guides/system/AppContainerConfig.java

AppContainerConfig.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020, 2021 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.system;
14
15import java.time.Duration;
16
17import org.microshed.testing.SharedContainerConfiguration;
18import org.microshed.testing.testcontainers.ApplicationContainer;
19import org.testcontainers.containers.KafkaContainer;
20import org.testcontainers.containers.Network;
21import org.testcontainers.junit.jupiter.Container;
22
23// tag::AppContainerConfig[]
24public class AppContainerConfig implements SharedContainerConfiguration {
25
26    private static Network network = Network.newNetwork();
27
28    // tag::container[]
29    // tag::kafka[]
30    @Container
31    // end::container[]
32    public static KafkaContainer kafka = new KafkaContainer()
33                    .withNetwork(network);
34    // end::kafka[]
35
36    // tag::container2[]
37    // tag::system[]
38    @Container
39    // end::container2[]
40    public static ApplicationContainer system = new ApplicationContainer()
41                    .withAppContextRoot("/")
42                    .withExposedPorts(9083)
43                    .withReadinessPath("/health/ready")
44                    .withNetwork(network)
45                    .withStartupTimeout(Duration.ofMinutes(3))
46                    // tag::dependsOn[]
47                    .dependsOn(kafka);
48                    // end::dependsOn[]
49    // end::system[]
50}
51// end::AppContainerConfig[]

The AppContainerConfig class externalizes test container setup and configuration, so you can use the same application containers across multiple tests.The @Container annotation denotes an application container that is started up and used in the tests.

Two containers are used for testing the system service: the system container, which you built, and the kafka container, which receives messages from the system service.

The dependsOn() method specifies that the system service container must wait until the kafka container is ready before it can start.

Testing your containers

Now you can start writing the test that uses the configured containers.

Create the SystemServiceIT class.
system/src/test/java/it/io/openliberty/guides/system/SystemServiceIT.java

SystemServiceIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020, 2021 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.system;
14
15import static org.junit.Assert.assertNotNull;
16
17import java.time.Duration;
18
19import org.apache.kafka.clients.consumer.ConsumerConfig;
20import org.apache.kafka.clients.consumer.ConsumerRecord;
21import org.apache.kafka.clients.consumer.ConsumerRecords;
22// tag::KafkaConsumer[]
23import org.apache.kafka.clients.consumer.KafkaConsumer;
24// end::KafkaConsumer[]
25import org.junit.jupiter.api.Test;
26import org.microshed.testing.SharedContainerConfig;
27import org.microshed.testing.jupiter.MicroShedTest;
28import org.microshed.testing.kafka.KafkaConsumerClient;
29
30import io.openliberty.guides.models.SystemLoad;
31import io.openliberty.guides.models.SystemLoad.SystemLoadDeserializer;
32
33@MicroShedTest
34@SharedContainerConfig(AppContainerConfig.class)
35public class SystemServiceIT {
36
37    // tag::KafkaConsumer2[]
38    // tag::KafkaConsumerClient[]
39    // tag::valueDeserializer[]
40    @KafkaConsumerClient(valueDeserializer = SystemLoadDeserializer.class,
41    // end::valueDeserializer[]
42                         groupId = "system-load-status",
43                         // tag::systemLoadTopic[]
44                         topics = "system.load",
45                         // end::systemLoadTopic[]
46                         properties = ConsumerConfig.AUTO_OFFSET_RESET_CONFIG
47                                      + "=earliest")
48    // end::KafkaConsumerClient[]
49    public static KafkaConsumer<String, SystemLoad> consumer;
50    // end::KafkaConsumer2[]
51
52    // tag::testCpuStatus[]
53    @Test
54    public void testCpuStatus() {
55        // tag::poll[]
56        ConsumerRecords<String, SystemLoad> records =
57                consumer.poll(Duration.ofMillis(30 * 1000));
58        // end::poll[]
59        System.out.println("Polled " + records.count() + " records from Kafka:");
60
61        for (ConsumerRecord<String, SystemLoad> record : records) {
62            SystemLoad sl = record.value();
63            System.out.println(sl);
64            // tag::assert[]
65            assertNotNull(sl.hostname);
66            assertNotNull(sl.loadAverage);
67            // end::assert[]
68        }
69
70        consumer.commitAsync();
71    }
72    // end::testCpuStatus[]
73}

SystemLoad.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.models;
14
15import java.util.Objects;
16
17import javax.json.bind.Jsonb;
18import javax.json.bind.JsonbBuilder;
19
20import org.apache.kafka.common.serialization.Deserializer;
21import org.apache.kafka.common.serialization.Serializer;
22
23public class SystemLoad {
24
25    private static final Jsonb jsonb = JsonbBuilder.create();
26
27    public String hostname;
28    public Double loadAverage;
29        
30    public SystemLoad(String hostname, Double cpuLoadAvg) {
31        this.hostname = hostname;
32        this.loadAverage = cpuLoadAvg;
33    }
34
35    public SystemLoad() {
36    }
37
38    @Override
39    public boolean equals(Object o) {
40        if (this == o) return true;
41        if (!(o instanceof SystemLoad)) return false;
42        SystemLoad sl = (SystemLoad) o;
43        return Objects.equals(hostname, sl.hostname)
44                && Objects.equals(loadAverage, sl.loadAverage);
45    }
46
47    @Override
48    public int hashCode() {
49        return Objects.hash(hostname, loadAverage);
50    }
51    
52    @Override
53    public String toString() {
54        return "CpuLoadAverage: " + jsonb.toJson(this);
55    }
56
57    // tag::SystemLoadSerializer[]
58    public static class SystemLoadSerializer implements Serializer<Object> {
59        @Override
60        public byte[] serialize(String topic, Object data) {
61          return jsonb.toJson(data).getBytes();
62        }
63    }
64    // end::SystemLoadSerializer[]
65
66    // tag::SystemLoadDeserializer[]
67    public static class SystemLoadDeserializer implements Deserializer<SystemLoad> {
68        @Override
69        public SystemLoad deserialize(String topic, byte[] data) {
70            if (data == null)
71                return null;
72            return jsonb.fromJson(new String(data), SystemLoad.class);
73        }
74    }
75    // end::SystemLoadDeserializer[]
76}

SystemService.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.system;
14
15import java.lang.management.ManagementFactory;
16import java.lang.management.OperatingSystemMXBean;
17import java.net.InetAddress;
18import java.net.UnknownHostException;
19import java.util.concurrent.TimeUnit;
20
21import javax.enterprise.context.ApplicationScoped;
22
23import org.eclipse.microprofile.reactive.messaging.Outgoing;
24import org.reactivestreams.Publisher;
25
26import io.openliberty.guides.models.SystemLoad;
27import io.reactivex.rxjava3.core.Flowable;
28
29@ApplicationScoped
30public class SystemService {
31
32    private static final OperatingSystemMXBean osMean = 
33            ManagementFactory.getOperatingSystemMXBean();
34    private static String hostname = null;
35
36    private static String getHostname() {
37        if (hostname == null) {
38            try {
39                return InetAddress.getLocalHost().getHostName();
40            } catch (UnknownHostException e) {
41                return System.getenv("HOSTNAME");
42            }
43        }
44        return hostname;
45    }
46
47    // tag::systemLoad[]
48    // tag::Outgoing[]
49    @Outgoing("systemLoad")
50    // end::Outgoing[]
51    public Publisher<SystemLoad> sendSystemLoad() {
52        return Flowable.interval(15, TimeUnit.SECONDS)
53                .map((interval -> new SystemLoad(getHostname(),
54                        osMean.getSystemLoadAverage())));
55    }
56    // end::systemLoad[]
57    
58}

The test uses the KafkaConsumer client API and is configured by using the @KafkaConsumerClient annotation. The consumer client is configured to consume messages from the system.load topic in the kafka container. To learn more about Kafka APIs and how to use them, check out the official Kafka Documentation.

To consume messages from a stream, the messages need to be deserialized from bytes. Kafka has its own default deserializer, but a custom deserializer is provided for you. The deserializer is configured to the consumer’s valueDeserializer and is implemented in the SystemLoad class.

The running system service container produces messages to the systemLoad Kafka topic, as denoted by the @Outgoing annotation. The testCpuStatus() test method polls a record from Kafka every 3 seconds until the timeout limit. It then verifies that the record polled matches the expected record.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You will see the following output:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 25.674 s - in it.io.openliberty.guides.system.SystemServiceIT

 Results:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

 Integration tests finished.

After you are finished running tests, stop the Open Liberty server by typing q in the command-line session where you ran the server, and then press the enter/return key.

If you aren’t running in dev mode, you can run the tests by running the following command:

mvn verify

You will see the following output:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 33.001 s - in it.io.openliberty.guides.system.SystemServiceIT

 Results:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0


 --- maven-failsafe-plugin:2.22.2:verify (verify) @ system ---
 ------------------------------------------------------------------------
 BUILD SUCCESS
 ------------------------------------------------------------------------
 Total time:  52.817 s
 Finished at: 2020-03-13T16:28:55-04:00
 ------------------------------------------------------------------------

Testing with the Kafka producer client

The inventory service is tested in the same way as the system service. The only difference is that the inventory service consumes messages, which means that tests are written to use the Kafka producer client.

Configuring your containers

Navigate to the start/inventory directory.

The AppContainerConfig class is provided, and it is configured in the same way as it was for the system service. The two containers that are configured for use in the inventory service integration test are the kafka and inventory containers.

Testing your containers

As you did with the system service, run Open Liberty in dev mode to listen for file changes:

mvn liberty:dev

Now you can create your integrated test.

Create the InventoryServiceIT class.
inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryServiceIT.java

InventoryServiceIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020, 2021 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.inventory;
14
15import java.math.BigDecimal;
16import java.util.List;
17import java.util.Properties;
18
19import javax.ws.rs.core.GenericType;
20import javax.ws.rs.core.Response;
21
22// tag::KafkaProducer[]
23import org.apache.kafka.clients.producer.KafkaProducer;
24// end::KafkaProducer[]
25import org.apache.kafka.clients.producer.ProducerRecord;
26import org.junit.jupiter.api.AfterAll;
27import org.junit.jupiter.api.Assertions;
28import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
29import org.junit.jupiter.api.Test;
30import org.junit.jupiter.api.TestMethodOrder;
31import org.microshed.testing.SharedContainerConfig;
32import org.microshed.testing.jaxrs.RESTClient;
33import org.microshed.testing.jupiter.MicroShedTest;
34import org.microshed.testing.kafka.KafkaProducerClient;
35
36import io.openliberty.guides.inventory.InventoryResource;
37import io.openliberty.guides.models.SystemLoad;
38import io.openliberty.guides.models.SystemLoad.SystemLoadSerializer;
39
40@MicroShedTest
41@SharedContainerConfig(AppContainerConfig.class)
42@TestMethodOrder(OrderAnnotation.class)
43// tag::InventoryServiceIT[]
44public class InventoryServiceIT {
45
46    // tag::RESTClient[]
47    @RESTClient
48    public static InventoryResource inventoryResource;
49    // end::RESTClient[]
50
51    // tag::KafkaProducer2[]
52    // tag::KafkaProducerClient[]
53    @KafkaProducerClient(valueSerializer = SystemLoadSerializer.class)
54    // end::KafkaProducerClient[]
55    public static KafkaProducer<String, SystemLoad> producer;
56    // end::KafkaProducer2[]
57
58    @AfterAll
59    public static void cleanup() {
60        inventoryResource.resetSystems();
61    }
62
63    // tag::testCpuUsage[]
64    @Test
65    public void testCpuUsage() throws InterruptedException {
66        SystemLoad sl = new SystemLoad("localhost", 1.1);
67        // tag::systemLoadTopic[]
68        producer.send(new ProducerRecord<String, SystemLoad>("system.load", sl));
69        // end::systemLoadTopic[]
70        Thread.sleep(5000);
71        Response response = inventoryResource.getSystems();
72        List<Properties> systems =
73                response.readEntity(new GenericType<List<Properties>>() { });
74        // tag::assert[]
75        Assertions.assertEquals(200, response.getStatus(),
76                "Response should be 200");
77        Assertions.assertEquals(systems.size(), 1);
78        // end::assert[]
79        for (Properties system : systems) {
80            // tag::assert2[]
81            Assertions.assertEquals(sl.hostname, system.get("hostname"),
82                    "Hostname doesn't match!");
83            // end::assert2[]
84            BigDecimal systemLoad = (BigDecimal) system.get("systemLoad");
85            // tag::assert3[]
86            Assertions.assertEquals(sl.loadAverage, systemLoad.doubleValue(),
87                    "CPU load doesn't match!");
88            // end::assert3[]
89        }
90    }
91    // end::testCpuUsage[]
92}
93// end::InventoryServiceIT[]

SystemLoad.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.models;
14
15import java.util.Objects;
16
17import javax.json.bind.Jsonb;
18import javax.json.bind.JsonbBuilder;
19
20import org.apache.kafka.common.serialization.Deserializer;
21import org.apache.kafka.common.serialization.Serializer;
22
23public class SystemLoad {
24
25    private static final Jsonb jsonb = JsonbBuilder.create();
26
27    public String hostname;
28    public Double loadAverage;
29        
30    public SystemLoad(String hostname, Double cpuLoadAvg) {
31        this.hostname = hostname;
32        this.loadAverage = cpuLoadAvg;
33    }
34
35    public SystemLoad() {
36    }
37
38    @Override
39    public boolean equals(Object o) {
40        if (this == o) return true;
41        if (!(o instanceof SystemLoad)) return false;
42        SystemLoad sl = (SystemLoad) o;
43        return Objects.equals(hostname, sl.hostname)
44                && Objects.equals(loadAverage, sl.loadAverage);
45    }
46
47    @Override
48    public int hashCode() {
49        return Objects.hash(hostname, loadAverage);
50    }
51    
52    @Override
53    public String toString() {
54        return "CpuLoadAverage: " + jsonb.toJson(this);
55    }
56
57    // tag::SystemLoadSerializer[]
58    public static class SystemLoadSerializer implements Serializer<Object> {
59        @Override
60        public byte[] serialize(String topic, Object data) {
61          return jsonb.toJson(data).getBytes();
62        }
63    }
64    // end::SystemLoadSerializer[]
65
66    // tag::SystemLoadDeserializer[]
67    public static class SystemLoadDeserializer implements Deserializer<SystemLoad> {
68        @Override
69        public SystemLoad deserialize(String topic, byte[] data) {
70            if (data == null)
71                return null;
72            return jsonb.fromJson(new String(data), SystemLoad.class);
73        }
74    }
75    // end::SystemLoadDeserializer[]
76}

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.inventory;
14
15import java.util.ArrayList;
16import java.util.List;
17import java.util.Optional;
18import java.util.Properties;
19import java.util.logging.Logger;
20
21import javax.enterprise.context.ApplicationScoped;
22import javax.inject.Inject;
23import javax.ws.rs.DELETE;
24import javax.ws.rs.GET;
25import javax.ws.rs.Path;
26import javax.ws.rs.PathParam;
27import javax.ws.rs.Produces;
28import javax.ws.rs.core.MediaType;
29import javax.ws.rs.core.Response;
30
31import org.eclipse.microprofile.reactive.messaging.Incoming;
32
33import io.openliberty.guides.models.SystemLoad;
34
35
36@ApplicationScoped
37@Path("/inventory")
38// tag::InventoryResource[]
39public class InventoryResource {
40
41    private static Logger logger = Logger.getLogger(InventoryResource.class.getName());
42
43    @Inject
44    private InventoryManager manager;
45    
46    @GET
47    @Path("/systems")
48    @Produces(MediaType.APPLICATION_JSON)
49    public Response getSystems() {
50        List<Properties> systems = new ArrayList<>(manager.getSystems().values());
51        return Response
52                .status(Response.Status.OK)
53                .entity(systems)
54                .build();
55    }
56
57    @GET
58    @Path("/system/{hostname}")
59    @Produces(MediaType.APPLICATION_JSON)
60    public Response getSystem(@PathParam("hostname") String hostname) {
61        Optional<Properties> system = manager.getSystem(hostname);
62        if (system.isPresent()) {
63            return Response
64                    .status(Response.Status.OK)
65                    .entity(system)
66                    .build();
67        }
68        return Response
69                .status(Response.Status.NOT_FOUND)
70                .entity("hostname does not exist.")
71                .build();
72    }
73
74    @DELETE
75    @Produces(MediaType.APPLICATION_JSON)
76    public Response resetSystems() {
77        manager.resetSystems();
78        return Response
79                .status(Response.Status.OK)
80                .build();
81    }
82
83    @Incoming("systemLoad")
84    public void updateStatus(SystemLoad s)  {
85        String hostname = s.hostname;
86        if (manager.getSystem(hostname).isPresent()) {
87            manager.updateCpuStatus(hostname, s.loadAverage);
88            logger.info("Host " + hostname + " was updated: " + s);
89        } else {
90            manager.addSystem(hostname, s.loadAverage);
91            logger.info("Host " + hostname + " was added: " + s);
92        }
93    }
94}
95// end::InventoryResource[]

The InventoryServiceIT class uses the KafkaProducer client API to produce messages in the test environment for the inventory service container to consume. The @KafkaProducerClient annotation configures the producer to use the custom serializer provided in the SystemLoad class. The @KafkaProducerClient annotation doesn’t include a topic that the client produces messages to because it has the flexibility to produce messages to any topic. In this example, it is configured to produce messages to the system.load topic.

The testCpuUsage test method produces a message to Kafka and then verifies that the response from the inventory service matches what is expected.

The @RESTClient annotation injects a REST client proxy of the InventoryResource class, which allows HTTP requests to be made to the running application. To learn more about REST clients, check out the Consuming RESTful services with template interfaces guide.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You will see the following output:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 32.564 s - in it.io.openliberty.guides.inventory.InventoryServiceIT

 Results:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

 Integration tests finished.

After you are finished running tests, stop the Open Liberty server by typing q in the command-line session where you ran the server, and then press the enter/return key.

If you aren’t running in dev mode, you can run the tests by running the following command:

mvn verify

You will see the following output:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 42.345 s - in it.io.openliberty.guides.inventory.InventoryServiceIT

 Results:

 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0


 --- maven-failsafe-plugin:2.22.2:verify (verify) @ inventory ---
 ------------------------------------------------------------------------
 BUILD SUCCESS
 ------------------------------------------------------------------------
 Total time:  48.213 s
 Finished at: 2020-03-13T16:43:34-04:00
 ------------------------------------------------------------------------

Great work! You’re done!

You just tested two reactive Java microservices using MicroShed Testing.

Learn more about MicroShed Testing.

Guide Attribution

Testing reactive Java microservices by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star