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 -->
            <!--            &lt;!&ndash;Define the JUnit5 bom. WildFly BOM still contains JUnit4, so we have to declare a version here &ndash;&gt;-->
            <!--            <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