Skip to content

Commit

Permalink
Merge pull request #731 from wildfly/fix_pr_725
Browse files Browse the repository at this point in the history
Update 2025-01-21-testing-on-docker-with-cube.adoc blog date
  • Loading branch information
jamezp authored Jan 27, 2025
2 parents 33c6ed9 + 71b8972 commit 6e1ab07
Showing 1 changed file with 387 additions and 0 deletions.
387 changes: 387 additions & 0 deletions _posts/2025-01-27-testing-on-docker-with-cube.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
---
layout: post
title: Testing WildFly applications on Docker with Arquillian Cube
date: 2025-01-27
tags: wildfly docker arquillian cube containers
synopsis: Arquillian Cube is an Arquillian extension that provides an easy way to test a WildFly containerized application on Docker
author: fabiobrz
---

= Testing WildFly applications on Docker with Arquillian Cube

Recently we resumed the https://github.com/arquillian/arquillian-cube[Arquillian Cube] project as a way to test on
containerized environments, such as Docker, Kubernetes, and OpenShift.
The last (pre)release is https://github.com/arquillian/arquillian-cube/releases/tag/2.0.0.Alpha1[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
https://www.wildfly.org/guides/get-started-microservices-on-kubernetes/simple-microservice-part1[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
https://www.wildfly.org/guides/get-started-microservices-on-kubernetes/simple-microservice-part1[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:

[source,shell]
----
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:

[source,yaml]
----
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:

[source,xml]
----
<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:
[source,xml]
----
<!-- 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:

[source,xml]
----
<!-- 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:

[source,xml]
----
<!-- 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:

[source,xml]
----
<!-- 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:

[source,xml]
----
<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:

[source,xml]
----
<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.

[source,xml]
----
<!-- <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:

[source,xml]
----
<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:

[source,java]
----
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:

[source,shell]
----
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:

[source,shell]
----
[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

0 comments on commit 6e1ab07

Please sign in to comment.