-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #731 from wildfly/fix_pr_725
Update 2025-01-21-testing-on-docker-with-cube.adoc blog date
- Loading branch information
Showing
1 changed file
with
387 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 --> | ||
<!-- <!–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: | ||
|
||
[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 |