これは、なにをしたくて書いたもの?
以前書いたこちらの記事の焼き直しです。
WildFly 25+Arquillian+JUnit 5でインテグレーションテスト(Managed/Remote/Bootable JAR) - CLOVER🍀
この時はJakarta EE 8(javax.〜
)でしたが、Jakarta EE 10になったこともあり、環境を変えて同じことをしてみようかなと。
Arquillian
ArquillianのWebサイトはこちら。
あまり更新がありませんが…。
とはいうものの、各種アプリケーションサーバーへの実装は継続しています。
- Tomcat: GitHub - arquillian/arquillian-container-tomcat: Arquillian Tomcat Containers
- Jetty: GitHub - arquillian/arquillian-container-jetty: Arquillian Jetty Containers
- GlassFish: GitHub - arquillian/arquillian-container-glassfish6: GlassFish 6+ Container integrations for the Arquillian Project
- GlassFishについてはリリースがなさそうです
- Payara: GitHub - payara/ecosystem-arquillian-connectors: Repository for Payara Arquillian connectors
- WildFly: GitHub - wildfly/wildfly-arquillian: The Wildfly Arquillian adaptor
- Open Liberty: GitHub - OpenLiberty/liberty-arquillian: Arquillian Liberty Managed and Remote containers
WildFlyとOpen LibertyはOrganzationが別ですね。
Arquillianのコアモジュールはこちら。
JUnit 5へ対応したモジュールも含まれています。
https://github.com/arquillian/arquillian-core/tree/1.8.0.Final/junit5
また、Jakarta EE 9、10に対応したモジュールもできているようです。
GitHub - arquillian/arquillian-jakarta
今回は、現時点での最新のWildFly 31.0.1.FinalとWildFly Arquillian Adapterを使って簡単なインテグレーションテストを書いてみたいと
思います。
現在のWildFly Arquillian Adapterのバージョンは5.0.1.Finalで、依存関係を見る限りJakarta EE 9以降がカバー範囲のようです。
https://github.com/wildfly/wildfly-arquillian/blob/5.0.1.Final/pom.xml#L64-L67
今回はJakarta EE 10を使うことになりますが、気にせず進めてみましょう。
内容自体は、基本的にはこちらの焼き直しです。
WildFly 25+Arquillian+JUnit 5でインテグレーションテスト(Managed/Remote/Bootable JAR) - CLOVER🍀
WildFly Arquillian AdapterにはManaged、Managed Domain、Embedded、Remote、Remote Domain、Bootableの6つのモジュールが
ありますが、今回はManaged、Remote、Bootableの3つを使います。
環境
今回の環境は、こちら。
$ java --version openjdk 21.0.2 2024-01-16 OpenJDK Runtime Environment (build 21.0.2+13-Ubuntu-122.04.1) OpenJDK 64-Bit Server VM (build 21.0.2+13-Ubuntu-122.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-97-generic", arch: "amd64", family: "unix"
テスト対象のプログラムの作成
まずは、テスト対象のプログラムを作成します。
Jakarta RESTful Web Services(JAX-RS)とJakarta Contexts and Dependency Injection(CDI)を使った簡単なプログラムにします。
pom.xml
から載せていきます。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>wildfly-arquillian-adapter-example</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-web-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.25.3</version> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>5.4.0</version> <scope>test</scope> </dependency> <!-- REST AssuredでJSONを扱うため--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.junit5</groupId> <artifactId>arquillian-junit5-container</artifactId> <version>1.8.0.Final</version> <scope>test</scope> </dependency> </dependencies> <profiles> <!-- あとで --> </profiles> </project>
プログラムの作成に必要な依存関係は、この部分のみです。
<dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-web-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency>
あとはテストに関する依存関係です。
JAX-RSの有効化。
src/main/java/org/littlewings/wildfly/arquillian/JaxrsActivator.java
package org.littlewings.wildfly.arquillian; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { }
JAX-RSリソースクラス。
src/main/java/org/littlewings/wildfly/arquillian/EchoResource.java
package org.littlewings.wildfly.arquillian; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.util.Map; @Path("echo") public class EchoResource { @Inject MessageService messageService; @GET @Produces(MediaType.TEXT_PLAIN) public String get(@QueryParam("message") String message) { return messageService.format(message); } @POST @Produces(MediaType.APPLICATION_JSON) public Map<String, Object> post(Map<String, Object> request) { return Map.of("message", messageService.format((String) request.get("message"))); } }
CDI管理Bean。
src/main/java/org/littlewings/wildfly/arquillian/MessageService.java
package org.littlewings.wildfly.arquillian; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class MessageService { public String format(String message) { return String.format("★★★ %s ★★★", message); } }
リクエストで受け取った文字列を、★で装飾して返すアプリケーションです。
動作イメージはこんな感じですね。
※これはBootable JARで動作させ、コンテキストパスが/
の状態で確認しています
$ curl localhost:8080/echo?message=Hello+World ★★★ Hello World ★★★ $ curl -XPOST -H 'Content-Type: application/json' localhost:8080/echo -d '{"message": "Hello World"}' {"message":"★★★ Hello World ★★★"}
テストコードの準備
テストは、今回作成したCDI管理Beanと、JAX-RSリソースクラスに対して作成します。
テスト向けのライブラリとしては、JUnit 5、AssertJ、REST Assuredを使います。
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.25.3</version> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>5.4.0</version> <scope>test</scope> </dependency> <!-- REST AssuredでJSONを扱うため--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> <scope>test</scope> </dependency>
Jacksonは、REST AssuredでJSONを扱う時に必要になります。
Jackson以外でもいいのですが、JSONを扱えるライブラリーが依存関係にないと、以下のように例外がスローされます。
java.lang.IllegalStateException: Cannot serialize object because no JSON serializer found in classpath. Please put Jackson (Databind), Gson, Johnzon, or Yasson in the classpath.
そして、こちらがArquillianでJUnit 5を使うためのモジュールです。
<dependency> <groupId>org.jboss.arquillian.junit5</groupId> <artifactId>arquillian-junit5-container</artifactId> <version>1.8.0.Final</version> <scope>test</scope> </dependency>
JUnit 5自体は、明示的に依存関係に追加する必要があります。
作成したテストコードはこちら。このテストは、Managed、Remote、Bootableすべてのパターンで使用します。
src/test/java/org/littlewings/wildfly/arquillian/WildFlyArquillianIT.java
package org.littlewings.wildfly.arquillian; import io.restassured.http.ContentType; import jakarta.inject.Inject; import jakarta.ws.rs.ApplicationPath; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.modules.maven.ArtifactCoordinates; import org.jboss.modules.maven.MavenResolver; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.io.IOException; import java.net.URL; import java.util.Map; import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; @ExtendWith(ArquillianExtension.class) class WildFlyArquillianIT { @Deployment static WebArchive createDeployment() throws IOException { return ShrinkWrap .create(WebArchive.class) .addPackages(true, JaxrsActivator.class.getPackage()) .addAsLibraries( MavenResolver .createDefaultResolver() .resolveJarArtifact(ArtifactCoordinates.fromString("org.assertj:assertj-core:3.25.3")) ); } @Inject MessageService messageService; @Test void cdiBeanTest() { assertThat(messageService.format("Hello World")) .isEqualTo("★★★ Hello World ★★★"); } @ArquillianResource URL deploymentUrl; String resourcePrefix = JaxrsActivator.class.getAnnotation(ApplicationPath.class).value(); @Test @RunAsClient void jaxrsResourceTest() { given() .queryParam("message", "Hello World") .when() .get(String.format("%s%s%s", deploymentUrl, resourcePrefix, "echo")) .then() .assertThat() .statusCode(200) .body(is("★★★ Hello World ★★★")); given() .contentType(ContentType.JSON) .body(Map.of("message", "Hello World")) .when() .post(String.format("%s%s%s", deploymentUrl, resourcePrefix, "echo")) .then() .assertThat() .statusCode(200) .body("message", is("★★★ Hello World ★★★")); } }
テストクラスには、Arquillianが提供するJUnit 5向けのExtensionを指定。
@ExtendWith(ArquillianExtension.class) class WildFlyArquillianIT {
Deployment
の作成。
@Deployment static WebArchive createDeployment() throws IOException { return ShrinkWrap .create(WebArchive.class) .addPackages(true, JaxrsActivator.class.getPackage()) .addAsLibraries( MavenResolver .createDefaultResolver() .resolveJarArtifact(ArtifactCoordinates.fromString("org.assertj:assertj-core:3.25.3")) ); }
デプロイ先で動かすCDI管理Beanのテスト。
@Inject MessageService messageService; @Test void cdiBeanTest() { assertThat(messageService.format("Hello World")) .isEqualTo("★★★ Hello World ★★★"); }
デプロイ後にクライアントとして動作させるJAX-RSリソースクラスに対するテスト。
@ArquillianResource URL deploymentUrl; String resourcePrefix = JaxrsActivator.class.getAnnotation(ApplicationPath.class).value(); @Test @RunAsClient void jaxrsResourceTest() { given() .queryParam("message", "Hello World") .when() .get(String.format("%s%s%s", deploymentUrl, resourcePrefix, "echo")) .then() .assertThat() .statusCode(200) .body(is("★★★ Hello World ★★★")); given() .contentType(ContentType.JSON) .body(Map.of("message", "Hello World")) .when() .post(String.format("%s%s%s", deploymentUrl, resourcePrefix, "echo")) .then() .assertThat() .statusCode(200) .body("message", is("★★★ Hello World ★★★")); }
なお、このままではコンパイルが通りません(具体的にはMavenResolver
とArtifactCoordinates
)。
コンパイルを通し実行させるようにするには、アプリケーションサーバー向けのライブラリーが必要になります。
ここからは、WildFly Arquillian Adapterの利用パターンごとにMavenのProfileを作成して組み込んでいきます。
pom.xml
の載せた時に省略していた以下の部分ですね。
<profiles> <!-- あとで --> </profiles>
WildFly Arquillian Adatperを組み込む
ここからは、Managed、Remote、Bootableの3パターンでWildFly Arquillian Adatperを組み込んでいきます。切り替え方は、MavenのProfileです。
Arquillianの設定ファイルはひとつにまとめ、システムプロパティarquillian.launch
で使用する設定を切り替えます。
src/test/resources/arquillian.xml
<?xml version="1.0" encoding="UTF-8" ?> <arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> <container qualifier="wildfly-managed" default="true"> <configuration> <property name="jbossHome">${local.wildfly.home}</property> <!-- deploy started managed server --> <property name="allowConnectingToRunningServer">${allow.connecting.to.running.server:false}</property> </configuration> </container> <container qualifier="wildfly-remote"> </container> <container qualifier="wildfly-bootable"> <configuration> <property name="jarFile">${bootable.jar}</property> </configuration> </container> </arquillian>
システムプロパティarquillian.launch
を使った切り替えは、こちらで紹介されています。
Container config runtime selection - Arquillian
デフォルトはManaged用のwildfly-managed
として、Remote用はwildfly-remote
、Bootable用はwildfly-bootable
としています。
また、${...}
のプレースホルダー部も、システムプロパティで指定します。
確認は、いずれもmvn verify
で行います。
ManagedおよびRemoteではWildFlyが必要になるので、ダウンロードしておきます。今回は31.0.1.Finalです。
$ curl -LO https://github.com/wildfly/wildfly/releases/download/31.0.1.Final/wildfly-31.0.1.Final.zip $ unzip wildfly-31.0.1.Final.zip $ cd wildfly-31.0.1.Final
Managed
まずはテスト実行時にWildFlyを起動、テストが終わったら停止する、Managed向けのProfileです。
<profile> <id>managed</id> <activation> <activeByDefault>true</activeByDefault> </activation> <dependencies> <dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-managed</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <configuration> <systemPropertyVariables> <!-- arquillian.xmlで default="true" にしているので省略可 --> <arquillian.launch>wildfly-managed</arquillian.launch> <local.wildfly.home>/path/to/wildfly-31.0.1.Final</local.wildfly.home> <!-- すでに起動しているWildFlyに接続する場合 --> <allow.connecting.to.running.server>false</allow.connecting.to.running.server> </systemPropertyVariables> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile>
Managed用のWilldFly Arquillian Adapter。
<dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-managed</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency>
Maven Failsafe Pluginでインテグレーションテストおよびシステムプロパティの設定。
<plugin> <artifactId>maven-failsafe-plugin</artifactId> <configuration> <systemPropertyVariables> <!-- arquillian.xmlで default="true" にしているので省略可 --> <arquillian.launch>wildfly-managed</arquillian.launch> <local.wildfly.home>/path/to/wildfly-31.0.1.Final</local.wildfly.home> <!-- すでに起動しているWildFlyに接続する場合 --> <allow.connecting.to.running.server>false</allow.connecting.to.running.server> </systemPropertyVariables> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> </execution> </executions> </plugin>
ここで指定したシステムプロパティですが
<systemPropertyVariables> <!-- arquillian.xmlで default="true" にしているので省略可 --> <arquillian.launch>wildfly-managed</arquillian.launch> <local.wildfly.home>/path/to/wildfly-31.0.1.Final</local.wildfly.home> <!-- すでに起動しているWildFlyに接続する場合 --> <allow.connecting.to.running.server>false</allow.connecting.to.running.server> </systemPropertyVariables>
arquillian.launch
はcontainer
タグのqualifier
の選択に、それ以外は${...}
で参照するように対応しています。
<container qualifier="wildfly-managed" default="true"> <configuration> <property name="jbossHome">${local.wildfly.home}</property> <!-- deploy started managed server --> <property name="allowConnectingToRunningServer">${allow.connecting.to.running.server:false}</property> </configuration> </container>
確認。
$ mvn verify ## または $ mvn verify -Pmanaged
これで、jbossHome
で指定した場所にあるWildFlyを起動してテストを行い、テストが終了したらWildFlyを停止します。
allowConnectingToRunningServer
をtrue
にした場合は、起動済みのWildFlyに対してデプロイおよびテストを行うようになります。
Remoteのような挙動ですね。
設定項目は、こちらを参考に。
Remote
次は起動済みのWildFlyに対してテストを行う、Remote向けのProfileです。
<profile> <id>remote</id> <dependencies> <dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-remote</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <configuration> <systemPropertyVariables> <arquillian.launch>wildfly-remote</arquillian.launch> </systemPropertyVariables> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile>
Remote用のWilldFly Arquillian Adapter。
<dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-remote</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency>
Maven Failsafe Pluginによるシステムプロパティの設定は、arquillian.launch
のみです。
<systemPropertyVariables> <arquillian.launch>wildfly-remote</arquillian.launch> </systemPropertyVariables>
WildFlyをローカルで動作させている場合、特に設定は不要です。
<container qualifier="wildfly-remote"> </container>
Remoteの場合は、先にWildFlyを起動して
$ bin/standalone.sh
テストを実行します。
$ mvn verify -Premote
これで、起動中のWildFlyに対してデプロイおよびテストが行われます。
Remoteの設定ですが、共通系以外の固有の設定はなさそうです。
Bootable
最後は、WildFly Bootable JARを作成後に起動してテストを行うBootable向けのProfileです。
<profile> <id>bootable</id> <dependencies> <dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-bootable</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <configuration> <systemPropertyVariables> <arquillian.launch>wildfly-bootable</arquillian.launch> <bootable.jar>target/${project.artifactId}-${project.version}-bootable.jar</bootable.jar> </systemPropertyVariables> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-jar-maven-plugin</artifactId> <version>10.0.0.Final</version> <configuration> <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#31.0.1.Final</feature-pack-location> <layers> <layer>jaxrs-server</layer> <layer>management</layer> </layers> <!-- for development <plugin-options> <jboss-maven-dist/> </plugin-options> --> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile>
Bootable向けのWildFly Arquillian Adapter。
<dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-bootable</artifactId> <version>5.0.1.Final</version> <scope>test</scope> </dependency>
システムプロパティとしては、arquillian.launch
と作成したWildFly Bootable JARのパスを定義しました。
<systemPropertyVariables> <arquillian.launch>wildfly-bootable</arquillian.launch> <bootable.jar>target/${project.artifactId}-${project.version}-bootable.jar</bootable.jar> </systemPropertyVariables>
WildFly Bootable JARへのパスは、jarFile
へ設定します。
<container qualifier="wildfly-bootable"> <configuration> <property name="jarFile">${bootable.jar}</property> </configuration> </container>
もちろん、WildFly Bootable JARを作成するためのWildFly JAR Pluginの設定も必要です。
<plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-jar-maven-plugin</artifactId> <version>10.0.0.Final</version> <configuration> <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#31.0.1.Final</feature-pack-location> <layers> <layer>jaxrs-server</layer> <layer>management</layer> </layers> <!-- for development <plugin-options> <jboss-maven-dist/> </plugin-options> --> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin>
あとはテストを実行すれば、WildFly Bootable JARを作成してテストが行われます。
$ mvn verify -Pbootable
ローカル動作確認用などで、JARファイルを軽量にしたい場合は以下のコメントアウトを解除するとよいでしょう。
<!-- for development <plugin-options> <jboss-maven-dist/> </plugin-options> -->
設定は、こちらを参照。
おわりに
Jakarta EE 10に対応したWildFlyを使って、Arquillian+JUnit 5でインテグレーションをテストをしてみました。
Jakarta EE 8の頃に試した時には情報を探すのやモジュールのバージョンの組み合わせでだいぶハマったのですが、今回はすんなりと
動かせてとても簡単だったので助かりました。