アプリケーションのテスト
Quarkusアプリケーションのテスト方法について説明します。 このガイドでは、以下の内容について説明します。
-
JVM モードでのテスト
-
ネイティブモードでのテスト
-
テストへのリソースの注入
1. 要件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.9
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
-
入門ガイドに掲載されている、完了済のGreeterアプリケーション
2. アーキテクチャー
このガイドでは、入門ガイドの一部として作成された最初のテストを拡張します。 テストへのインジェクションと、ネイティブ実行可能ファイルをテストする方法もカバーしています。
Quarkus は継続的テストをサポートしていますが、これについては 継続的テストガイド で説明しています。 |
3. ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリケーションを作成することを推奨します。 ただし、完成した例にそのまま進むこともできます。
Git リポジトリをクローンする: git clone https://github.com/quarkusio/quarkus-quickstarts.git
または アーカイブ をダウンロードする。
ソリューションは getting-started-testing
ディレクトリー にあります。
このガイドでは、 getting-started
ディレクトリーの完成したアプリケーションをすでに持っていることを前提としています。
4. JVM モードでの HTTP ベースのテストの要約
「はじめに」のサンプルから始めた場合は、正しいツールマップの設定を含めて、 すでにテストが完了しているはずです。
ビルドファイルには、2 つのテスト依存関係が表示されます。
quarkus-junit5
は、テストフレームワークを制御する @QuarkusTest
アノテーションを提供するため、テストには必須です。
rest-assured
は必須ではありませんが、HTTP エンドポイントをテストするための便利な方法です。また、正しい URL を自動的
に設定するインテグレーション機能も提供しているため、設定は必要ありません。
JUnit 5を使用しているので、Surefire Maven Plugin のバージョンを設定する必要があります。デフォルトのバージョンはJUnit 5をサポートしていない為です。
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
また、テストが正しいログマネージャーを使用するように java.util.logging.manager
システムプロパティーを設定し、
${maven.home}/conf/settings.xml
からのカスタム設定 (存在する場合) が適用されるように maven.home
も設定します。
プロジェクトには簡単なテストも含まれているはずです:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
}
このテストはHTTPを使用して、RESTエンドポイントを直接テストします。テストが実行されると、 テストが実行される前にアプリケーションが開始されます。
4.1. テストポートの制御
Quarkusはデフォルトではポート 8080
をリッスンしますが、テストを実行する場合はデフォルトで 8081
をリッスンします。
これにより、アプリケーションを並行して実行しながらテストを実行することができます。
テストポートの変更
|
Quarkusはまた、テストを実行する前にRestAssuredによって使用されるデフォルトのポートを更新するRestAssuredインテグレーションも提供しているため、 追加の設定は必要ありません。
4.2. HTTP インタラクションタイムアウトの制御
テストで REST Assured を使用する場合、接続と応答のタイムアウトは 30 秒に設定されます。
この設定は quarkus.http.test-timeout
プロパティーでオーバーライドできます:
quarkus.http.test-timeout=10s
4.3. URI の挿入
URLをテストに直接注入することも可能で、別のクライアントを使用するのが簡単になります。
これは @TestHTTPResource
アノテーションで行います。
静的なリソースをロードするための簡単なテストを書いてみましょう。まず、シンプルなHTMLファイルを
src/main/resources/META-INF/resources/index.html
に作成します:
<html>
<head>
<title>Testing Guide</title>
</head>
<body>
Information about testing
</body>
</html>
これが正しく提供されているかどうかを確認するための簡単なテストを作成します:
package org.acme.getting.started.testing;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class StaticContentTest {
@TestHTTPResource("index.html") (1)
URL url;
@Test
public void testIndexHtml() throws IOException {
try (InputStream in = url.openStream()) {
String contents = new String(in.readAllBytes(), StandardCharsets.UTF_8);
Assertions.assertTrue(contents.contains("<title>Testing Guide</title>"));
}
}
}
1 | このアノテーションを使用すると、QuarkusインスタンスのURLを直接注入することができます。アノテーションの値は、URLのパス部分になります。 |
今のところ @TestHTTPResource
では、URL の URI
, URL
, String
表現を注入することができます。
5. 特定のエンドポイントのテスト
RESTassuredと @TestHTTPResource
の両方が、パスをハードコーディングするのではなく、テストするエンドポイントクラスを指定することをサポートしています。現在、Jakarta RESTエンドポイント、Servlet、Reactive Routesの両方をサポートしています。これにより、あるテストがどのエンドポイント
をテストしているのかを正確に把握することが非常に容易になります。
これらの例では、以下のようなエンドポイントを想定しています:
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
現在、Jakarta RESTのコンテキストパスを設定するための @ApplicationPath() アノテーションをサポートしていません。カスタムコンテキストパスが必要な場合は、
代わりに quarkus.resteasy.path の設定値を使用してください。
|
5.1. テストHTTPリソース
io.quarkus.test.common.http.TestHTTPEndpoint
アノテーションを使用してエンドポイントのパスを指定することが出来、
指定されたエンドポイントからパスが抽出されます。 TestHTTPResource
エンドポイントにも値を指定すると、
エンドポイントパスの最後に追加されます。
package org.acme.getting.started.testing;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class StaticContentTest {
@TestHTTPEndpoint(GreetingResource.class) (1)
@TestHTTPResource
URL url;
@Test
public void testIndexHtml() throws IOException {
try (InputStream in = url.openStream()) {
String contents = new String(in.readAllBytes(), StandardCharsets.UTF_8);
Assertions.assertEquals("hello", contents);
}
}
}
1 | GreetingResource は @Path("/hello") とアノテーションされているので、注入された URL は
/hello で終わります。 |
5.2. RESTassured
RESTassured ベースパス (すなわち、すべてのリクエストのルートとなるデフォルトパス) を制御するには、
io.quarkus.test.common.http.TestHTTPEndpoint
アノテーションを使用できます。これはクラスやメソッドレベルで適用できます。
グリーティングリソースをテストするには、以下のようにします:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.when;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
@TestHTTPEndpoint(GreetingResource.class) (1)
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
when().get() (2)
.then()
.statusCode(200)
.body(is("hello"));
}
}
1 | これにより、RESTAssured はすべてのリクエストの前に /hello を付けます。 |
2 | このテストでは /hello がデフォルトなので、ここでパスを指定する必要はないことに注意してください。 |
6. テストへの注入
これまでは、HTTP エンドポイントを介してアプリをテストする統合スタイルのテストしか取り上げてきませんでしたが、 ユニットテストを行い、Beanを直接テストしたい場合はどうでしょうか?
Quarkusでは、 @Inject
アノテーションを介してテストにCDI Beanを注入できるようにすることで、これをサポートしています(実際、Quarkusのテストは完全なCDI Beanなので、すべてのCDI機能を使用することができます)。HTTPを使用せずにグリーティングサービスを直接テストするシンプルなテストを作成してみましょう。
package org.acme.getting.started.testing;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class GreetingServiceTest {
@Inject (1)
GreetingService service;
@Test
public void testGreetingService() {
Assertions.assertEquals("hello Quarkus", service.greeting("Quarkus"));
}
}
1 | GreetingService Beanがテストに注入されます。 |
@SessionScoped Bean を注入/テストする場合、セッションコンテキストがアクティブではない可能性が高く、注入された Bean のメソッドが呼び出されたときに ContextNotActiveException を受け取ることになります。ただし、 @io.quarkus.test.ActivateSessionContext インターセプターバインディングを使用して、特定のビジネスメソッドのセッションコンテキストをアクティブ化することは可能です。その他の制限については、javadoc を参照してください。
|
7. テストへのインターセプターの適用
前述したように、Quarkusのテストは実際には完全なCDI Beanであり、通常のようにCDIインターセプターを適用することができます。例えば、トランザクションのコンテキスト内でテストメソッドを実行したい場合、 @Transactional
アノテーションをメソッドに適用するだけで、トランザクションインターセプターがそれを処理します。
これに加えて、独自のテストステレオタイプを作成することもできます。例えば、以下のように @TransactionalQuarkusTest
を作成することができます。
@QuarkusTest
@Stereotype
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransactionalQuarkusTest {
}
このアノテーションをテストクラスに適用すると、 @QuarkusTest
と @Transactional
の両方のアノテーションを適用したかのように動作します。
@TransactionalQuarkusTest
public class TestStereotypeTestCase {
@Inject
UserTransaction userTransaction;
@Test
public void testUserTransaction() throws Exception {
Assertions.assertEquals(Status.STATUS_ACTIVE, userTransaction.getStatus());
}
}
8. テストとトランザクション
テストでは標準のQuarkus @Transactional
アノテーションを使用することができますが、これは、テストでデータベースに加えた変更が永続化されることを意味します。テストの終了時に変更をロールバックしたい場合は、 io.quarkus.test.TestTransaction
アノテーションを使用することができます。これは、トランザクション内でテストメソッドを実行しますが、テストメソッドが完了したらロールバックして、データベースの変更を元に戻します。
9. QuarkusTest* コールバックによる強化
インターセプターの代わりに、あるいはインターセプターに加えて、以下のコールバックインターフェースを実装することで、 すべての @QuarkusTest
クラスを充実させることができます。
-
io.quarkus.test.junit.callback.QuarkusTestBeforeClassCallback
-
io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback
-
io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
-
io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback
-
io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback
-
io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
オプションとして、プロパティ quarkus.test.enable-callbacks-for-integration-tests
を true
とすることで、これらのコールバックを @QuarkusIntegrationTest
テストでも有効にすることができます。
このようなコールバックの実装は、 java.util.ServiceLoader
で定義されている「サービスプロバイダ」として登録する必要があります。
例えば、以下のようなサンプルコールバックです:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
public class MyQuarkusTestBeforeEachCallback implements QuarkusTestBeforeEachCallback {
@Override
public void beforeEach(QuarkusTestMethodContext context) {
System.out.println("Executing " + context.getTestMethod());
}
}
次のように、 src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
を介して登録する必要があります。
org.acme.getting.started.testing.MyQuarkusTestBeforeEachCallback
テストクラスやメソッドからアノテーションを読み込んで、コールバックが何をするかを制御することができます。 |
10. 異なるプロファイルのテスト
これまでの例では、すべてのテストで Quarkus を 1 回だけ起動しました。最初のテストが実行される前に Quarkus が起動し、その後すべてのテストが実行され、最後に Quarkus がシャットダウンされます。これにより、非常に高速なテストが可能になりますが、様々な設定をテストすることができないため、少し制限されます。
この問題を回避するために、Quarkusはテストプロファイルの考え方をサポートしています。以前に実行したテストとは異なるプロファイルを持つテストがある場合、Quarkusはテストを実行する前にシャットダウンされ、新しいプロファイルで開始されます。これは、テスト時間にシャットダウン/起動サイクルが追加されるため、明らかに少し遅くなりますが、非常に大きな柔軟性が得られます。
Quarkus の再起動回数を減らすために、 io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer
は、JUnit 5 ユーザーガイド に記載
されているように、グローバル ClassOrderer
として登録されます。
この ClassOrderer
の動作は、 quarkus.test.class-orderer
プロパティーを使用して application.properties
経由で設定できます。
このプロパティーは、使用する ClassOrderer
の FQCN を受け入れます。クラスが見つからない場合は、
JUnit のデフォルトの動作にフォールバックし、 ClassOrderer
をまったく設定しません。また、JUnit 5 が提供する別の ClassOrderer
や、独自のカスタム ClassOrderer を設定することで、完全に無効にすることもできます。
10.1. プロフィールの書き方
テスト プロファイルを実装するには、 io.quarkus.test.junit.QuarkusTestProfile
を実装する必要があります:
package org.acme.getting.started.testing;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.enterprise.inject.Produces;
import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.QuarkusTestProfile.TestResourceEntry;
public class MockGreetingProfile implements QuarkusTestProfile { (1)
/**
* Returns additional config to be applied to the test. This
* will override any existing config (including in application.properties),
* however existing config will be merged with this (i.e. application.properties
* config will still take effect, unless a specific config key has been overridden).
*
* Here we are changing the Jakarta REST root path.
*/
@Override
public Map<String, String> getConfigOverrides() {
return Collections.singletonMap("quarkus.resteasy.path","/api");
}
/**
* Returns enabled alternatives.
*
* This has the same effect as setting the 'quarkus.arc.selected-alternatives' config key,
* however it may be more convenient.
*/
@Override
public Set<Class<?>> getEnabledAlternatives() {
return Collections.singleton(MockGreetingService.class);
}
/**
* Allows the default config profile to be overridden. This basically just sets the quarkus.test.profile system
* property before the test is run.
*
* Here we are setting the profile to test-mocked
*/
@Override
public String getConfigProfile() {
return "test-mocked";
}
/**
* Additional {@link QuarkusTestResourceLifecycleManager} classes (along with their init params) to be used from this
* specific test profile.
*
* If this method is not overridden, then only the {@link QuarkusTestResourceLifecycleManager} classes enabled via the {@link io.quarkus.test.common.QuarkusTestResource} class
* annotation will be used for the tests using this profile (which is the same behavior as tests that don't use a profile at all).
*/
@Override
public List<TestResourceEntry> testResources() {
return Collections.singletonList(new TestResourceEntry(CustomWireMockServerManager.class));
}
/**
* If this returns true then only the test resources returned from {@link #testResources()} will be started,
* global annotated test resources will be ignored.
*/
@Override
public boolean disableGlobalTestResources() {
return false;
}
/**
* The tags this profile is associated with.
* When the {@code quarkus.test.profile.tags} System property is set (its value is a comma separated list of strings)
* then Quarkus will only execute tests that are annotated with a {@code @TestProfile} that has at least one of the
* supplied (via the aforementioned system property) tags.
*/
@Override
public Set<String> tags() {
return Collections.emptySet();
}
/**
* The command line parameters that are passed to the main method on startup.
*/
@Override
public String[] commandLineParameters() {
return new String[0];
}
/**
* If the main method should be run.
*/
@Override
public boolean runMainMethod() {
return false;
}
/**
* If this method returns true then all {@code StartupEvent} and {@code ShutdownEvent} observers declared on application
* beans should be disabled.
*/
@Override
public boolean disableApplicationLifecycleObservers() {
return false;
}
@Produces (2)
public ExternalService mockExternalService() {
return new ExternalService("mock");
}
}
1 | これらのメソッドにはすべてデフォルトの実装があるため、オーバーライドが必要なメソッドのみオーバーライドします。 |
2 | テストプロファイルの実装で CDI Bean が宣言されている場合 (プロデューサーメソッド/フィールドまたはネストされた静的クラス経由)、この Bean はテストプロファイルが使用されている場合にのみ考慮され、他のテストプロファイルでは無視されます。 |
これでプロファイルを定義したので、それをテストクラスに含める必要があります。そのためには、テストクラスに @TestProfile(MockGreetingProfile.class)
アノテーションを付けます。
テストプロファイルの設定はすべて単一のクラスに保存されているので、前回のテストが同じ設定で実行されたかどうかが簡単にわかります。
10.2. 特定のテストの実行
Quarkus は、テストの実行を特定の @TestProfile
アノテーションを持つテストに制限できます。これは、 QuarkusTestProfile
の tags
メソッドを quarkus.test.profile.tags
システムプロパティーと組み合わせて利用することで機能します。
基本的に、 quarkus.test.profile.tags
の値と一致するタグが少なくとも 1 つある QuarkusTestProfile
はアクティブであると見なされ、アクティブなプロファイルの @TestProfile
でアノテーションが付けられたすべてのテストが実行されますが、それ以外はスキップされます。以下は、その典型的な例です。
まず、次のようないくつかの QuarkusTestProfile
実装を定義しましょう。
public class Profiles {
public static class NoTags implements QuarkusTestProfile {
}
public static class SingleTag implements QuarkusTestProfile {
@Override
public Set<String> tags() {
return Set.of("test1");
}
}
public static class MultipleTags implements QuarkusTestProfile {
@Override
public Set<String> tags() {
return Set.of("test1", "test2");
}
}
}
ここで、次のテストがあると仮定します。
@QuarkusTest
public class NoQuarkusProfileTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.NoTags.class)
public class NoTagsTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.SingleTag.class)
public class SingleTagTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.MultipleTags.class)
public class MultipleTagsTest {
@Test
public void test() {
// test something
}
}
次のシナリオを考えてみましょう。
-
quarkus.test.profile.tags
が設定されていない: すべてのテストが実行されます。 -
quarkus.test.profile.tags=foo
: この場合、QuarkusTestProfile
実装で定義されたタグはいずれもquarkus.test.profile.tags
の値と一致しないため、テストは実行されません。@TestProfile
でアノテーションが付けられていないため、NoQuarkusProfileTest
も実行されないことに注意してください。 -
quarkus.test.profile.tags=test1
: この場合、それぞれのQuarkusTestProfile
実装のタグがquarkus.test.profile.tags
の値と一致するため、SingleTagTest
とMultipleTagsTest
が実行されます。 -
quarkus.test.profile.tags=test1,test3
: この場合、前の場合と同じテストが実行されます。 -
quarkus.test.profile.tags=test2,test3
: この場合、MultipleTagsTest
は、tags
メソッドがquarkus.test.profile.tags
の値と一致する唯一のQuarkusTestProfile
実装であるため、MultipleTagsTest
のみ実行されます。
11. ネストしたテスト
JUnit 5 @Nested tests are useful for structuring more complex test scenarios. However, note that it is not possible to assign different test profiles or resources to nested tests within the same parent class.
12. モックサポート
Quarkusでは、2つの異なるアプローチを使用したモックオブジェクトの使用をサポートしています。CDIの代替品を使用してすべてのテストクラスのBeanをモックアウトするか、 QuarkusMock
を使用してテストごとにBeanをモックアウトすることができます。
12.1. CDI @Alternative
メカニズム
これを使用するには、 src/test/java
ディレクトリーのクラスでモックしたいBeanをオーバーライドし、 @Alternative
と @Priority(1)
アノテーションをBeanに配置するだけです。あるいは、便利な io.quarkus.test.Mock
ステレオタイプアノテーションを使用することもできます。この組み込みステレオタイプは、 @Alternative
、 @Priority(1)
、 @Dependent
を宣言します。例えば、以下のようなサービスがあるとします:
@ApplicationScoped
public class ExternalService {
public String service() {
return "external";
}
}
src/test/java
で以下のクラスでモックできました:
@Mock
@ApplicationScoped (1)
public class MockExternalService extends ExternalService {
@Override
public String service() {
return "mock";
}
}
1 | @Mock ステレオタイプで宣言された @Dependent スコープをオーバーライドします。 |
代替品が src/main/java
ではなく src/test/java
ディレクトリーに存在することが重要です。そうでなければ、テスト以外も常に有効になってしまいます。
現時点では、このアプローチはネイティブイメージテストでは機能しないことに注意してください。これには、代替テストをネイティブイメージに焼き付ける必要があるためです。
12.2. QuarkusMock を使用したモッキング
io.quarkus.test.junit.QuarkusMock
クラスは、通常のスコープ付きBeanを一時的にモックアウトするために使用することができます。 @BeforeAll
メソッドでこのメソッドを使用した場合、モックは現在のクラスのすべてのテストに対して有効になりますが、test メソッドでこれを使用した場合、モックは現在のテストの間のみ有効になります。
この方法は、通常のスコープ付き CDI Bean (例: @ApplicationScoped
、 @RequestScoped
など、 @Singleton
と @Dependent
以外の基本的にすべてのスコープ) に対して使用することができます。
使用例は次のようになります:
@QuarkusTest
public class MockTestCase {
@Inject
MockableBean1 mockableBean1;
@Inject
MockableBean2 mockableBean2;
@BeforeAll
public static void setup() {
MockableBean1 mock = Mockito.mock(MockableBean1.class);
Mockito.when(mock.greet("Stuart")).thenReturn("A mock for Stuart");
QuarkusMock.installMockForType(mock, MockableBean1.class); (1)
}
@Test
public void testBeforeAll() {
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Hello Stuart", mockableBean2.greet("Stuart"));
}
@Test
public void testPerTestMock() {
QuarkusMock.installMockForInstance(new BonjourGreeter(), mockableBean2); (2)
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Bonjour Stuart", mockableBean2.greet("Stuart"));
}
@ApplicationScoped
public static class MockableBean1 {
public String greet(String name) {
return "Hello " + name;
}
}
@ApplicationScoped
public static class MockableBean2 {
public String greet(String name) {
return "Hello " + name;
}
}
public static class BonjourGreeter extends MockableBean2 {
@Override
public String greet(String name) {
return "Bonjour " + name;
}
}
}
1 | インジェクションされたインスタンスはここでは利用できないので、 installMockForType を使用します。このモックは両方のテストメソッドに使用されます。 |
2 | 私たちは installMockForInstance を使用して注入されたBeanを置き換えます。 |
Mockitoには依存しないことに注意してください。好きなモッキングライブラリを使うことができますし、必要な動作を提供するためにオブジェクトを手動でオーバーライドすることもできます。
@Inject を使用すると、インストールしたモックインスタンスへの CDI プロキシーが取得されますが、これは Mockito.verify などのメソッドに渡すのには適していません。
これらのメソッドはモックインスタンスそのものを要求するためです。
したがって、 verify などのメソッドを呼び出す必要がある場合は、テストでモックインスタンスを保持するか、 @io.quarkus.test.InjectMock を使用する必要があります。
|
12.2.1. @InjectMock
での更なる単純化
QuarkusMock
が提供する機能を基に構築されている Quarkus では、ユーザーが Mockito を簡単に利用して、 QuarkusMock
でサポートされている Bean をモックすることもできます。
この機能は、
|
@InjectMock
を使用すると、先ほどの例は次のように書くことができます:
@QuarkusTest
public class MockTestCase {
@InjectMock
MockableBean1 mockableBean1; (1)
@InjectMock
MockableBean2 mockableBean2;
@BeforeEach
public void setup() {
Mockito.when(mockableBean1.greet("Stuart")).thenReturn("A mock for Stuart"); (2)
}
@Test
public void firstTest() {
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals(null, mockableBean2.greet("Stuart")); (3)
}
@Test
public void secondTest() {
Mockito.when(mockableBean2.greet("Stuart")).thenReturn("Bonjour Stuart"); (4)
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Bonjour Stuart", mockableBean2.greet("Stuart"));
}
@ApplicationScoped
public static class MockableBean1 {
public String greet(String name) {
return "Hello " + name;
}
}
@ApplicationScoped
public static class MockableBean2 {
public String greet(String name) {
return "Hello " + name;
}
}
}
1 | @InjectMock を使用すると、Mockitoのモックが作成され、テストクラスのテストメソッドで利用できるようになります(他のテストクラスはこの影響を 受けません )。 |
2 | クラスのすべてのテストメソッドに対して mockableBean1 が設定されています。 |
3 | mockableBean2 のモックが設定されていないので、デフォルトの Mockito レスポンスを返します。 |
4 | このテストでは、 mockableBean2 が設定されているので、設定されたレスポンスを返します。 |
上のテストは @InjectMock
の機能を示す場合に適していますが、実際のテストを上手く表してはいません。実際のテストでは、ほとんどの場合、モックを設定し、モックされた Bean を使用する Bean をテストします。以下に例を示します。
@QuarkusTest
public class MockGreetingServiceTest {
@InjectMock
GreetingService greetingService;
@Test
public void testGreeting() {
when(greetingService.greet()).thenReturn("hi");
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hi")); (1)
}
@Path("greeting")
public static class GreetingResource {
final GreetingService greetingService;
public GreetingResource(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GET
@Produces("text/plain")
public String greet() {
return greetingService.greet();
}
}
@ApplicationScoped
public static class GreetingService {
public String greet(){
return "hello";
}
}
}
1 | greetingService をモックとして設定したので、 GreetingService Beanを使用する GreetingResource は、通常の GreetingService Beanのレスポンスの代わりにモックされたレスポンスを取得します。 |
デフォルトでは、 @InjectMock
アノテーションは、通常の CDI スコープ Bean (例: @ApplicationScoped
、 @RequestScoped
) に使用できます。
@Singleton
Bean のモック化は、 @MockitoConfig(convertScopes = true)
アノテーションを追加することで実行できます。
これにより、テストのために @Singleton
Bean が @ApplicationScoped
Bean に変換されます。
これは高度なオプションと見なされ、Bean のスコープを変更することで引き起こされる結果を完全に理解している場合にのみ実行する必要があります。
12.2.2. @InjectSpy
で、モックの代わりにスパイを使用する
InjectMock
で提供されている機能をベースに、 QuarkusMock
でサポートされているBeanをスパイするために Mockito を簡単に利用できるようにしました。この機能は、 quarkus-junit5-mockito
依存関係で利用可能な @io.quarkus.test.junit.mockito.InjectSpy
アノテーションを介して利用できます。
テストの際に、特定の論理パスが通ったことを確認するだけでよい場合や、Spyされたクローン上の残りのメソッドを実行したまま、単一のメソッドのレスポンスをスタブアウトするだけでよい場合があります。Spy部分モックの詳細については、 Mockitoドキュメント - Spying on real objects を参照してください。いずれの場合も、オブジェクトのSpyを使用することが望ましいです。 @InjectSpy
を使うと、先ほどの例は次のように書くことができます:
@QuarkusTest
public class SpyGreetingServiceTest {
@InjectSpy
GreetingService greetingService;
@Test
public void testDefaultGreeting() {
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hello"));
Mockito.verify(greetingService, Mockito.times(1)).greet(); (1)
}
@Test
public void testOverrideGreeting() {
doReturn("hi").when(greetingService).greet(); (2)
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hi")); (3)
}
@Path("greeting")
public static class GreetingResource {
final GreetingService greetingService;
public GreetingResource(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GET
@Produces("text/plain")
public String greet() {
return greetingService.greet();
}
}
@ApplicationScoped
public static class GreetingService {
public String greet(){
return "hello";
}
}
}
1 | 値をオーバーライドするのではなく、 GreetingService の greet メソッドがこのテストで呼び出されたことを確認したいだけです。 |
2 | ここでは、Spyに "hello "の代わりに "hi "を返すように指示しています。 GreetingResource が GreetingService に挨拶を要求すると、通常の GreetingService Bean の応答の代わりに、モックされた応答が返ってきます。スパイをスタブするために when(Object) を使用することが不可能であったり、現実的でなかったりすることがあります。したがって、spyを使用する場合は、スタブ用のメソッドである doReturn|Answer|Throw() ファミリーを検討してください。 |
3 | 私たちは、スパイからのモックされた応答を得ることを検証しています。 |
12.2.3. @InjectMock
との併用 @RestClient
@RegisterRestClient
は、ランタイムに REST Client の実装を登録します。Beanは通常のスコープである必要があるため、インターフェイスに @ApplicationScoped
アノテーションを付ける必要があります。
@Path("/")
@ApplicationScoped
@RegisterRestClient
public interface GreetingService {
@GET
@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
String hello();
}
テストクラスの例です:
@QuarkusTest
public class GreetingResourceTest {
@InjectMock
@RestClient (1)
GreetingService greetingService;
@Test
public void testHelloEndpoint() {
Mockito.when(greetingService.hello()).thenReturn("hello from mockito");
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello from mockito"));
}
}
1 | この注入ポイントが RestClient のインスタンスを使用することを意味していることを示します。 |
12.3. Mocking with Panache
quarkus-hibernate-orm-panache
や quarkus-mongodb-panache
のエクステンションを使っている場合は、Hibernate ORM with Panache モック および MongoDB with Panache モックのドキュメントをチェックして、データアクセスをモックする最も簡単な方法を確認してください。
13. セキュリティーのテスト
Quarkus Securityを使用している場合、アプリケーションのセキュリティー機能を簡単にテストする方法については、セキュリティーのテスト のセクションをご覧ください。
14. Quarkus アプリケーションの開始前にサービスを開始する
非常に一般的なニーズとして、Quarkus アプリケーションをテスト用に起動する前に、Quarkus アプリケーションが依存するいくつかのサービスを起動することが挙げられます。このニーズに対応するために、Quarkusでは、 @io.quarkus.test.common.QuarkusTestResource
と io.quarkus.test.common.QuarkusTestResourceLifecycleManager
を提供します。
テストスイート内の任意のテストに @QuarkusTestResource
というアノテーションを付けるだけで、Quarkus はテストが実行される前に、対応する QuarkusTestResourceLifecycleManager
を実行します。
テストスイートでは、複数の @QuarkusTestResource
アノテーションを自由に利用することもできます。その場合、対応するすべての QuarkusTestResourceLifecycleManager
オブジェクトがテストの前に実行されます。
テストリソースは、テストクラスまたはカスタムプロファイルで定義されている場合でもグローバルです。つまり、重複を削除しても、すべてのテストですべてアクティブになります。
単一のテストクラスまたはテストプロファイルに限定してテストリソースを有効にする場合は、 @QuarkusTestResource(restrictToAnnotatedClass = true) を使用できます。
|
複数のテストリソースを使用する場合、それらを同時に開始できます。そのためには、 @QuarkusTestResource(parallel = true) を設定する必要があります。
|
Quarkus では、 QuarkusTestResourceLifecycleManager
の実装がいくつか提供されていますが (H2 データベースを起動する io.quarkus.test.h2.H2DatabaseTestResource
や、モック Kubernetes API サーバーを起動する io.quarkus.test.kubernetes.client.KubernetesServerTestResource
を参照)、
特定のアプリケーションのニーズに対応するためにカスタム実装を作成するのが一般的です。
一般的なケースとしては、Testcontainers を使用した docker コンテナーの起動 (その例は こちら を参照)、
Wiremock を使用したモック HTTP サーバの起動 (その例は こちら を参照) などがあります。
QuarkusTestResourceLifecycleManager は CDI Bean ではないため、これを実装するクラスには @Inject で注入されたフィールドを含めることはできません。 String propertyName = ConfigProvider.getConfig().getValue("quarkus.my-config-group.myconfig", String.class); を使用できます。
|
14.1. テストクラスの変更
テストクラスに何かを注入する必要があるカスタムの QuarkusTestResourceLifecycleManager
を作成する場合、 inject
メソッドを使用できます。
たとえば、次のようなテストがある場合などです。
@QuarkusTest
@QuarkusTestResource(MyWireMockResource.class)
public class MyTest {
@InjectWireMock // this a custom annotation you are defining in your own application
WireMockServer wireMockServer;
@Test
public someTest() {
// control wiremock in some way and perform test
}
}
次のコードスニペットの inject
メソッドに示すように、 MyWireMockResource
に wireMockServer
フィールドを注入させることができます:
public class MyWireMockResource implements QuarkusTestResourceLifecycleManager {
WireMockServer wireMockServer;
@Override
public Map<String, String> start() {
wireMockServer = new WireMockServer(8090);
wireMockServer.start();
// create some stubs
return Map.of("some.service.url", "localhost:" + wireMockServer.port());
}
@Override
public synchronized void stop() {
if (wireMockServer != null) {
wireMockServer.stop();
wireMockServer = null;
}
}
@Override
public void inject(TestInjector testInjector) {
testInjector.injectIntoFields(wireMockServer, new TestInjector.AnnotatedAndMatchesType(InjectWireMock.class, WireMockServer.class));
}
}
テストクラスへのこの注入は CDI の制御下になく、CDI がテストクラスへの必要な注入を実行した後に発生することは言及に値します。 |
14.2. アノテーションベースのテストリソース
アノテーションを使用して有効化および設定されたテストリソースを作成することができます。これは、テストリソースの有効化と設定に使用されるアノテーション に @QuarkusTestResource
を配置することで可能になります。
たとえば、これは @WithKubernetesTestServer
アノテーションを定義します。これは、テストで KubernetesServerTestResource
をアクティブ化するために使用できますが、アノテーションが付けられたテストクラスに限定されます。これは QuarkusTestProfile
テストプロファイルに配置することもできます。
@QuarkusTestResource(KubernetesServerTestResource.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WithKubernetesTestServer {
/**
* Start it with HTTPS
*/
boolean https() default false;
/**
* Start it in CRUD mode
*/
boolean crud() default true;
/**
* Port to use, defaults to any available port
*/
int port() default 0;
}
KubernetesServerTestResource
クラスは、前のアノテーションを使用して設定するために、 QuarkusTestResourceConfigurableLifecycleManager
インターフェイスを実装する必要があります。
public class KubernetesServerTestResource
implements QuarkusTestResourceConfigurableLifecycleManager<WithKubernetesTestServer> {
private boolean https = false;
private boolean crud = true;
private int port = 0;
@Override
public void init(WithKubernetesTestServer annotation) {
this.https = annotation.https();
this.crud = annotation.crud();
this.port = annotation.port();
}
// ...
}
アノテーションを繰り返し使えるようにするには、包含するアノテーション型に @QuarkusTestResourceRepeatable
を付ける必要があります。例えば、この場合、繰り返し使える @WithRepeatableTestResource
アノテーションを定義することになります。
@QuarkusTestResource(KubernetesServerTestResource.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(WithRepeatableTestResource.List.class)
public @interface WithRepeatableTestResource {
String key() default "";
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@QuarkusTestResourceRepeatable(WithRepeatableTestResource.class)
@interface List {
WithRepeatableTestResource[] value();
}
}
14.3. @WithTestResource
の使用法
@QuarkusTestResource
によって提供されるテストリソースは、グローバルに使用できるか、またはアノテーション付きテストクラス (restrictToAnnotatedClass
) に制限されますが、アノテーション @WithTestResource
を使用すると、実行用にテストリソース別にテストをさらにグループ化できます。
@WithTestResource
には、 TestResourceScope
列挙値を取る scope
プロパティーがあります。
-
TestResourceScope.MATCHING_RESOURCES
(デフォルト): Quarkus は同じテストリソースを持つテストをグループ化し、まとめてに実行します。グループが実行されると、すべてのテストリソースが停止され、次のグループが実行されます。 -
TestResourceScope.RESTRICTED_TO_CLASS
: テストリソースは、アノテーションが付けられたテストクラスでのみ使用でき、テストクラスが実行されると停止されます。 -
TestResourceScope.GLOBAL
: テストリソースはテストスイート内のすべてのテストに適用されます
次のいずれかに該当する場合は、Quarkus を再起動する必要があります。
-
現在のテストのテストリソースの少なくとも 1 つが、テストクラスに制限されている
-
次のテストのテストリソースの少なくとも 1 つが、テストクラスに制限されている
-
異なる
MATCHING_RESOURCES
スコープのテストリソースが使用されている
15. ハング検出
@QuarkusTest
は、予期しないハングを診断するために使用できるハング検出をサポートしています。指定された時間内に進捗がない場合 (つまり、JUnit コールバックが呼び出されない場合)、Quarkus はスタックトレースをコンソールに出力して、ハングの診断を助けます。このタイムアウトのデフォルト値は 10 分です。
これ以上のアクションは実行されず、テストは通常どおり (通常は CI がタイムアウトするまで) 続行されますが、出力されたスタックトレースは、ビルドが失敗した理由を診断するのに役立ちます。このタイムアウトは、 quarkus.test.hang-detection-timeout
システムプロパティーで制御できます (これは application.properties でも設定できますが、Quarkus が起動するまで読み取られないため、Quarkus の起動タイムアウトはデフォルトの 10 分です)。
16. ネイティブ実行可能ファイルテスト
@QuarkusIntegrationTest
を使用してネイティブ実行可能ファイルをテストすることも可能です。これは、テストに注入すること (そして、ネイティブ実行可能ファイルは別の非 JVM プロセスで実行されることーこれは実際には可能ではありません) を除いて、このガイドで述べたすべての機能をサポートしています。
これについては、ネイティブ実行可能ファイルガイド で説明されています。
17. @QuarkusIntegrationTest
の使用
@QuarkusIntegrationTest
は、Quarkus ビルドによって生成されたアーティファクトを起動およびテストするために使用する必要があり、jar (タイプを問わず)、ネイティブイメージ、またはコンテナーイメージのテストをサポートします。
簡単に言えば、Quarkus ビルド (mvn package
または gradle build
) の結果が jar である場合、その jar は java -jar …
として起動され、それに対して実行がテストされることを意味します。
代わりにネイティブイメージがビルドされた場合、アプリケーションは ./application …
として起動され、実行中のアプリケーションに対して実行がテストされます。
最後に、ビルド中に (quarkus-container-image-jib
、 quarkus-container-image-docker
または container-image-podman
エクステンションを含め、 quarkus.container-image.build=true
プロパティーを設定することで) コンテナーイメージが作成された場合、コンテナーが作成されて実行されます (これには、 docker
または podman
の実行可能ファイルが存在する必要があります)。
これは、同じ機能セットをサポートし、同じ制限を持つブラックボックステストです。
|
pom.xml
ファイルには以下が含まれます。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
これは、failsafe-maven-plugin に統合テストを実行するように指示します。
次に、 src/test/java/org/acme/quickstart/GreetingResourceIT.java
を開きます。次の内容が含まれています:
package org.acme.quickstart;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest (1)
public class GreetingResourceIT extends GreetingResourceTest { (2)
// Run the same tests
}
1 | テストの前にネイティブファイルからアプリケーションを起動する別のテストランナーを使用します。実行可能ファイルは、Failsafe Maven Plugin によって取得されます。 |
2 | 便宜上、以前のテストを拡張していますが、独自のテストを実装することもできます。 |
詳細については、次のリンクを参照してください: Testing the native executable Guide.
|
|
17.1. コンテナーの起動
@QuarkusIntegrationTest
によってコンテナーが起動されると (quarkus.container-image.build
が true
に設定されているためにアプリケーションがビルドされたため)、コンテナーは予測可能なコンテナーネットワーク上で起動されます。これにより、アプリケーションをサポートするためにサービスを起動する必要があるインテグレーションテストの作成が容易になります。
これは、 @QuarkusIntegrationTest
が、Dev Services を介して起動されたコンテナーでそのまま機能することを意味しますが、追加のコンテナーを起動する QuarkusTestLifecycleManager リソースを使用して有効化することも意味します。
これは、 QuarkusTestLifecycleManager
に io.quarkus.test.common.DevServicesContext.ContextAware
を実装することで実現できます。以下は、その簡単な例です。
テストするリソースを実行しているコンテナー (たとえば Testcontainers を介した PostgreSQL) には、コンテナーのネットワークから IP アドレスが割り当てられます。コンテナーのネットワークからの「パブリック」IP と「マップされていない」ポート番号を使用してサービスに接続します。Testcontainers ライブラリーは通常、コンテナーネットワークを尊重せずに接続文字列を返すため、コンテナーネットワーク上のコンテナーの IP と_マップされていない_ポート番号を使用して Quarkus に正しい接続文字列を提供するには、追加のコードが必要です。
次の例は PostgreSQL での使用法を示していますが、このアプローチはすべてのコンテナーに適用できます。
import io.quarkus.test.common.DevServicesContext;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.PostgreSQLContainer;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CustomResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware {
private Optional<String> containerNetworkId;
private JdbcDatabaseContainer container;
@Override
public void setIntegrationTestContext(DevServicesContext context) {
containerNetworkId = context.containerNetworkId();
}
@Override
public Map<String, String> start() {
// start a container making sure to call withNetworkMode() with the value of containerNetworkId if present
container = new PostgreSQLContainer<>("postgres:latest").withLogConsumer(outputFrame -> {});
// apply the network to the container
containerNetworkId.ifPresent(container::withNetworkMode);
// start container before retrieving its URL or other properties
container.start();
String jdbcUrl = container.getJdbcUrl();
if (containerNetworkId.isPresent()) {
// Replace hostname + port in the provided JDBC URL with the hostname of the Docker container
// running PostgreSQL and the listening port.
jdbcUrl = fixJdbcUrl(jdbcUrl);
}
// return a map containing the configuration the application needs to use the service
return ImmutableMap.of(
"quarkus.datasource.username", container.getUsername(),
"quarkus.datasource.password", container.getPassword(),
"quarkus.datasource.jdbc.url", jdbcUrl);
}
private String fixJdbcUrl(String jdbcUrl) {
// Part of the JDBC URL to replace
String hostPort = container.getHost() + ':' + container.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT);
// Host/IP on the container network plus the unmapped port
String networkHostPort =
container.getCurrentContainerInfo().getConfig().getHostName()
+ ':'
+ PostgreSQLContainer.POSTGRESQL_PORT;
return jdbcUrl.replace(hostPort, networkHostPort);
}
@Override
public void stop() {
// close container
}
}
このドキュメントの対応するセクションで説明されているように、 CustomResource
は @QuarkusTestResource
を使用して @QuarkusIntegrationTest
でアクティブ化されます。
17.2. 実行中のアプリケーションに対するテストの実行
@QuarkusIntegrationTest
は、アプリケーションのすでに実行中のインスタンスに対するテストの実行をサポートします。これは、テストの実行時に
quarkus.http.test-host
システムプロパティーを設定することで実現できます。
次の Maven コマンドはその使用例で、 @QuarkusIntegrationTest
を強制的に実行し、 http://1.2.3.4:4321
からアクセスできます:
./mvnw verify -Dquarkus.http.test-host=1.2.3.4 -Dquarkus.http.test-port=4321
SSL/TLS 接続のみを受け入れる実行中のインスタンス (例: https://1.2.3.4:4321
) に対してテストするには、システムプロパティー quarkus.http.test-ssl-enabled
を true
に設定します。
18. @QuarkusTest
と他のタイプのテストを混合
@QuarkusTest
でアノテーションが付けられたテストと、 @QuarkusDevModeTest
、 @QuarkusProdModeTest
、 @QuarkusUnitTest
のいずれかでアノテーションが付けられたテストを、1 回の実行 (たとえば、1 回の Maven Surefire プラグインの実行) で混合することはできません。ただし、後者の 3 つは共存できます。
この制限の理由は、 @QuarkusTest
がテスト実行の期間全体にわたって Quarkus サーバーを起動するため、他のテストが独自の Quarkus サーバーを起動できないようにするためです。
この制限を緩和するために、 @QuarkusTest
アノテーションは JUnit5 @Tag
を定義します: io.quarkus.test.junit.QuarkusTest
。このタグを使用して、特定の実行 で @QuarkusTest
テストを分離できます。以下は Maven Surefire プラグインを使用した例です。
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<id>default-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludedGroups>io.quarkus.test.junit.QuarkusTest</excludedGroups>
</configuration>
</execution>
<execution>
<id>quarkus-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>io.quarkus.test.junit.QuarkusTest</groups>
</configuration>
</execution>
</executions>
<configuration>
<systemProperties>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemProperties>
</configuration>
</plugin>
現在、 Maven の場合、前者は surefire プラグインによって、後者は failsafe プラグインによって実行される必要があることを意味します。 Gradle の場合、これは 2 種類のテストが異なるソースセットに属する必要があることを意味します。 ソースセットの設定例
|
19. IDE から @QuarkusTest
を実行する
ほとんどの IDE では、選択したクラスを JUnit テストとして直接実行できるようになっています。そのためには、選択した IDE の設定でいくつかのプロパティーを設定する必要があります:
-
java.util.logging.manager
(ロギングガイド を参照) -
maven.home
(${maven.home}/conf/settings.xml
にカスタム設定がある場合のみ)、Maven Guide を参照) -
maven.settings
(カスタム版のsettings.xml
ファイルをテストに使用する場合)
19.1. Eclipse の別個の JRE 定義
現在の"Installed JRE"定義を新しい定義にコピーし、新しいVMの引数としてプロパティーを追加します:
-
-Djava.util.logging.manager=org.jboss.logmanager.LogManager
-
-Dmaven.home=<path-to-your-maven-installation>
このJRE定義をQuarkusプロジェクトのターゲットランタイムとして使用すると、「Run as JUnit」設定に回避策が適用されます。
20. 開発サービスのテスト
デフォルトでは、テストは Dev Services でのみ機能するはずですが、一部のユースケースでは、テストで自動的に設定されたプロパティーにアクセスする必要がある場合があります。
これは、 @QuarkusTest
または @QuarkusIntegrationTest
に直接注入できる io.quarkus.test.common.DevServicesContext
を使用して行うことができます。タイプ DevServicesContext
のフィールドを定義するだけで、自動的に注入されます。これを使用して、設定されている任意のプロパティーを取得できます。通常、これは、テスト自体からリソースに直接接続するために使用されます。たとえば、kafka に接続して、テスト対象のアプリケーションにメッセージを送信するために使用されます。
io.quarkus.test.common.DevServicesContext.ContextAware
を実装するオブジェクトへの注入もサポートされています。 io.quarkus.test.common.DevServicesContext.ContextAware
を実装するフィールドがある場合、Quarkus は ` setIntegrationTestContext` メソッドを呼び出して、コンテキストをこのオブジェクトに渡します。これにより、クライアントロジックをユーティリティークラスにカプセル化できます。
QuarkusTestResourceLifecycleManager
実装は、 ContextAware
を実装してこれらのプロパティーにアクセスすることもできます。これにより、Quarkus が起動する前にリソースを設定できます (たとえば、KeyCloak インスタンスの設定、データベースへのデータの追加など)。
アプリケーションをコンテナーとしてランチャーにする |
21. コンポーネントのテスト
Quarkus は、コンポーネントのテストと依存関係のモック作成を容易にする JUnit エクステンションである QuarkusComponentTestExtension
を提供します。
この JUnit エクステンションは、 quarkus-junit5-component
依存関係で利用できます。
2 つの注入ポイントを持つ CDI Bean であるコンポーネント Foo
があるとします。
Foo
コンポーネントpackage org.acme;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped (1)
public class Foo {
@Inject
Charlie charlie; (2)
@ConfigProperty(name = "bar")
boolean bar; (3)
public String ping() {
return bar ? charlie.ping() : "nok";
}
}
1 | Foo は @ApplicationScoped CDI Bean です。 |
2 | Foo は、メソッド ping() を宣言する Charlie に依存します。 |
3 | Foo は設定プロパティー bar に依存します。 @Inject は CDI 修飾子も宣言するため、この注入ポイントには必要ありません。これは Quarkus 固有の機能です。 |
この場合、コンポーネントテストは次のようになります。
import static org.junit.jupiter.api.Assertions.assertEquals;
import jakarta.inject.Inject;
import io.quarkus.test.InjectMock;
import io.quarkus.test.component.TestConfigProperty;
import io.quarkus.test.component.QuarkusComponentTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@QuarkusComponentTest (1)
@TestConfigProperty(key = "bar", value = "true") (2)
public class FooTest {
@Inject
Foo foo; (3)
@InjectMock
Charlie charlieMock; (4)
@Test
public void testPing() {
Mockito.when(charlieMock.ping()).thenReturn("OK"); (5)
assertEquals("OK", foo.ping());
}
}
1 | QuarkusComponentTest アノテーションは JUnit エクステンションを登録します。 |
2 | テストの設定プロパティーを設定します。 |
3 | このテストはテスト対象のコンポーネントを注入します。 @Inject でアノテーションが付けられたすべてのフィールドの型が、テスト対象のコンポーネント型とみなされます。 @QuarkusComponentTest#value() を使用して追加のコンポーネントクラスを指定することもできます。さらに、テストクラスで宣言された静的ネストクラスもコンポーネントです。 |
4 | このテストは Charlie のモックも挿入します。 Charlie は、 @Singleton 合成 Bean が自動的に登録される、満たされていない 依存関係です。挿入される参照は、"未設定" の Mockito モックです。 |
5 | テストメソッドで Mockito API を活用して動作を設定できます。 |
さらに多くの例とヒントについては、 テストコンポーネントリファレンスガイド を参照してください。