Testing WildFly applications on Docker with Arquillian Cube
Recently we resumed the Arquillian Cube project as a way to test on containerized environments, such as Docker, Kubernetes, and OpenShift. The last (pre)release is 2.0.0.Alpha1, and it aims at filling the gap with the 1.18.2 release, by running against more recent versions of target environments (again, Docker, Kubernetes and OpenShift).
This example provides the guidance to set up an automated integration test for a WildFly application that should be run on Docker.
In order to do so, we’ll start from the WildFly Java Microservice - PART 1: Docker Image guide, which will be modified to show how to implement a JUnit test that will use an existing Dockerfile to automate the image build and execution.
Use case
The original article uses Maven archetypes to provide the reader with a ready-to-use WildFly application project. And it works great, definitely!
But in the last section, the user is instructed on how to build the Docker image, and about testing it manually. This article is all about this: using Arquillian Cube to automate the image build, and the Docker container setup and execution, while leveraging annotations and APIs at the test class level, to let the developer focus on the actual test logic.
In the following sections we’ll see which steps we need to take in order to modify the original example and achieve what above.
Step by step changes
As said, we need to start from the WildFly Java Microservice - PART 1: Docker Image article, so make sure to go through it, then…
Docker compose
and Dockerfile resources
Let’s start and create a new docker-build
directory at the project root:
mkdir docker-build
and put the Dockerfile - i.e. the one we created in the original article - inside it. We’ll need to change just one
line, i.e. we should replace the path target/server
with just server
, we’ll see why later on.
As you can see, we reused the Dockerfile which we created in the original example, which defines how the image should be built.
Then we should create a docker-compose.yml
file at the project root, as well, with the following contents:
version: '2'
services:
wildfly:
build:
context: docker-build
ports:
- "9991:9990"
- "8081:8080"
networks:
- front-tier
networks:
front-tier:
Here we’ve defined how the Docker container should run the image created previously.
Specifically, we can see that a container named wildfly
will be started, building an image as per the Dockerfile
which is in the docker-build
sub-directory, and exposing the container 8080
and 9990
ports via the host’s
8081
and 9991
ones, respectively.
That’s all about what we need on the Docker side. Arquillian Cube will automate the docker compose
build that
will use the Dockerfile which was created already to build and run the WildFly application image.
In the following section we’ll modify the project POM to use Arquillian Cube.
Update the example project POM
First off, let’s add the following properties
to define some required versions:
<arquillian-cube.version>2.0.0.Alpha1</arquillian-cube.version>
<arquillian-core.version>1.8.0.Final</arquillian-core.version>
<junit.version>4.13.2</junit.version>
<slf4j.version>2.0.16</slf4j.version>
Then we need to comment out, or remove, the following declaration of the JUnit 5 BOM off the dependencyManagement
section, since Arquillian Cube will use JUnit 4 instrumentation by default:
<!-- Arquillian Cube still using JUnit 4 by default -->
<!-- <!–Define the JUnit5 bom. WildFly BOM still contains JUnit4, so we have to declare a version here –>-->
<!-- <dependency>-->
<!-- <groupId>org.junit</groupId>-->
<!-- <artifactId>junit-bom</artifactId>-->
<!-- <version>${version.junit5}</version>-->
<!-- <type>pom</type>-->
<!-- <scope>import</scope>-->
<!-- </dependency>-->
and finally let’s add the following dependencies to the dependencyManagment
section, which are what we need to use
Arquillian Cube:
<!-- We need to lock Arquillian Cube dependencies to 2.0.0.Alpha1 -->
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-bom</artifactId>
<type>pom</type>
<scope>import</scope>
<version>${arquillian-cube.version}</version>
</dependency>
<!-- And Arquillian Core one to the 1.8.0. version, which is the one that Arquillian Cube 2.0.0.Alpha1 is using -->
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<type>pom</type>
<scope>import</scope>
<version>${arquillian-core.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-bom</artifactId>
<version>${slf4j-api.version}</version>
<scope>test</scope>
</dependency>
Once this is done, we actually need to depend on Arquillian Cube and related artifacts, which we’ll do by adding the
following to the dependencies
section:
<!-- We need Arquillian Cube to run our WildFly instance in a Docker container -->
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-docker</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- Slf4j is used by Arquillian Cube Docker -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
while we’ll have to remove the following ones:
<!-- Test scope dependencies -->
<!-- Arquillian Cube still using JUnit 4 by default -->
<!-- <dependency>-->
<!-- <groupId>org.junit.jupiter</groupId>-->
<!-- <artifactId>junit-jupiter</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<!-- Not needed anymore because the test uses a standalone Docker container -->
<!-- <dependency>-->
<!-- <groupId>org.wildfly.arquillian</groupId>-->
<!-- <artifactId>wildfly-arquillian-container-managed</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
Last moves with our POM, let’s add the following to the wildfly-maven-plugin
configuration:
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>${version.wildfly.maven.plugin}</version>
<configuration>
<!-- We need for the server to be provisioned in ./docker-build/server, as required by the Dockerfile -->
<provisioningDir>${project.basedir}/docker-build/server</provisioningDir>
<overwriteProvisionedServer>true</overwriteProvisionedServer>
and let the maven-clean-plugin
take care of such directory when cleaning things up, too:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.2</version>
<!-- Let's remove ./docker-build/server, too -->
<configuration>
<filesets>
<fileset>
<directory>${project.basedir}/docker-build/server</directory>
</fileset>
</filesets>
</configuration>
</plugin>
That’s it, we’re done with the POM, let’s move on and see how the arquillian.xml
file should be configured.
Update arquillian.xml
configuration
This is easy, we don’t need a wildfly
container anymore, so let’s remove it.
<!-- <container default="true" qualifier="managed"> -->
<!-- <configuration> -->
<!-- <property name="jbossHome">target/server</property> -->
<!-- </configuration> -->
<!-- </container> -->
Then we need to configure the docker
extension, specifically we’ll just set the dockerContainersFile
property,
i.e. the path for the docker-compose.yml
file:
<extension qualifier="docker">
<property name="dockerContainersFile">./docker-compose.yml</property>
</extension>
With all the above in place, the only thing left is the test class.
Create a test class for testing on Docker
Add the following contents to a new GettingStartedDockerIT.java
class:
package org.wildfly.examples;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.Response;
import org.arquillian.cube.HostIp;
import org.arquillian.cube.HostPort;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.URI;
/**
* Run integration tests with Arquillian to be able to test CDI beans
*/
@RunWith(Arquillian.class)
public class GettingStartedDockerIT {
@HostIp
private String wildflyIp;
@HostPort(containerName = "wildfly", value = 8080)
int wildflyPort;
@Test
public void testHelloEndpoint() {
try (Client client = ClientBuilder.newClient()) {
final String name = "World";
Response response = client
.target(URI.create("http://" + wildflyIp + ":" + wildflyPort + "/"))
.path("/hello/" + name)
.request()
.get();
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(String.format("Hello '%s'.", name), response.readEntity(String.class));
}
}
}
As you can see, it’s similar to the existing GettingStartedApplicationIT.java
test class that the Maven archetype
execution created for us in the original example, but we use a different runner, and inject the Docker container IP
address and the host port which is mapping the exposed 8080
port.
At this point we can remove the two existing test classes, i.e. GettingStartedServiceIT
and
GettingStartedApplicationIT.java
.
Run the test
That’s it, we can run Docker integration test by issuing the following command:
mvn clean install
and we’ll see how Arquillian Cube will gather the docker extension configuration, then summarize the container definition, and eventually run the test:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.wildfly.examples.GettingStartedDockerIT
...
Jan 20, 2025 6:06:06 PM org.arquillian.cube.docker.impl.client.CubeDockerConfigurationResolver resolveSystemDefaultSetup
INFO: Connected to docker (fburzigo-thinkpadp1gen3.rmtit.csb) using default settings version: 24.0.5 kernel: 6.11.4-201.fc40.x86_64
CubeDockerConfiguration:
serverUri = unix:///var/run/docker.sock
tlsVerify = false
dockerServerIp = localhost
definitionFormat = COMPOSE
clean = false
removeVolumes = true
dockerContainers = containers:
wildfly:
alwaysPull: false
buildImage:
dockerfileLocation: docker-build
noCache: true
remove: true
killContainer: false
manual: false
networkMode: front-tier
networks:
- front-tier
portBindings: !!set
9991->9990/tcp: null
8081->8080/tcp: null
readonlyRootfs: false
removeVolumes: true
networks:
front-tier:
driver: bridge
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 16.69 s -- in org.wildfly.examples.GettingStartedDockerIT
In conclusion
Testing a WildFly application directly on Docker will make the test more similar to the actual environment where it will be run. Arquillian Cube provides an easy and effective way to test on Docker, with almost no configuration and instrumentation changes with respect to existing Arquillian based tests.
The code for the example application which is described in this article is here: https://github.com/fabiobrz/wildfly-mini-series-docker-cube
Fabio Burzigotti