アプリケーションのテスト
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
、または アーカイブ をダウンロードします。
The solution is located in the getting-started-testing
directory.
このガイドでは、 getting-started
ディレクトリーの完成したアプリケーションをすでに持っていることを前提としています。
4. JVM モードでの HTTP ベースのテストの要約
「はじめに」のサンプルから始めた場合は、正しいツールマップの設定を含めて、すでにテストが完了しているはずです。
ビルドファイルには、2 つのテスト依存関係が表示されます。
quarkus-junit5
は、テストフレームワークを制御する @QuarkusTest
アノテーションを提供するため、テストには必須です。 rest-assured
は必須ではありませんが、HTTP エンドポイントをテストするのに便利な方法です。
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
システムプロパティーを設定して、テストが正しい logmanager と maven.home
を使用して、 ${maven.home}/conf/settings.xml
からのカスタム設定が適用されるようにしています (存在する場合)。
プロジェクトには簡単なテストも含まれているはずです:
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がテストに注入されます。 |
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
テストクラスやメソッドからアノテーションを読み込んで、コールバックが何をするかを制御することができます。 |
While it is possible to use JUnit Jupiter callback interfaces like BeforeEachCallback , you might run into classloading issues because Quarkus has
to run tests in a custom classloader which JUnit is not aware of.
|
10. 異なるプロファイルのテスト
これまでの例では、すべてのテストで Quarkus を 1 回だけ起動しました。最初のテストが実行される前に Quarkus が起動し、その後すべてのテストが実行され、最後に Quarkus がシャットダウンされます。これにより、非常に高速なテストが可能になりますが、様々な設定をテストすることができないため、少し制限されます。
この問題を回避するために、Quarkusはテストプロファイルの考え方をサポートしています。以前に実行したテストとは異なるプロファイルを持つテストがある場合、Quarkusはテストを実行する前にシャットダウンされ、新しいプロファイルで開始されます。これは、テスト時間にシャットダウン/起動サイクルが追加されるため、明らかに少し遅くなりますが、非常に大きな柔軟性が得られます。
Quarkus の再起動回数を減らすために、JUnit 5 User Guide にあるように io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer
がグローバルな ClassOrderer
として登録されています。この ClassOrderer
の動作は junit-platform.properties
で設定することができます (詳細はソースコードや javadoc を参照してください)。また、JUnit 5 が提供する別の ClassOrderer
を設定することで、完全に無効にすることも可能です (独自のものを設定することも可能です)。さらに JUnit 5.8.2 の時点は、単一の junit-platform.properties
のみがピックアップされ、複数見つかった場合は警告が記録されることに注意してください。 このような警告が発生した場合は、Quarkus が提供する junit-platform.properties
をクラスパスから除外することで、警告を取り除くことができます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-properties</artifactId>
</exclusion>
</exclusions>
</dependency>
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 | If a test profile implementation declares a CDI bean (via producer method/field or nested static class) then this bean is only taken into account if the test profile is used, i.e. it’s ignored for any other test profile. |
これでプロファイルを定義したので、それをテストクラスに含める必要があります。そのためには、テストクラスに @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. モックサポート
Quarkusでは、2つの異なるアプローチを使用したモックオブジェクトの使用をサポートしています。CDIの代替品を使用してすべてのテストクラスのBeanをモックアウトするか、 QuarkusMock
を使用してテストごとにBeanをモックアウトすることができます。
11.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
ディレクトリーに存在することが重要です。そうでなければ、テスト以外も常に有効になってしまいます。
現時点では、このアプローチはネイティブイメージテストでは機能しないことに注意してください。これには、代替テストをネイティブイメージに焼き付ける必要があるためです。
11.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には依存しないことに注意してください。好きなモッキングライブラリを使うことができますし、必要な動作を提供するためにオブジェクトを手動でオーバーライドすることもできます。
Using @Inject will get you a CDI proxy to the mock instance you install, which is not suitable for passing to methods such as Mockito.verify
which want the mock instance itself.
So if you need to call methods such as verify you should hang on to the mock instance in your test, or use @io.quarkus.test.InjectMock .
|
11.2.1. @InjectMock
での更なる単純化
Building on the features provided by QuarkusMock
, Quarkus also allows users to effortlessly take advantage of Mockito for mocking the beans supported by QuarkusMock
.
This functionality is available with the
|
@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のレスポンスの代わりにモックされたレスポンスを取得します。 |
By default, the @InjectMock
annotation can be used for any normal CDI scoped bean (e.g. @ApplicationScoped
, @RequestScoped
).
Mocking @Singleton
beans can be performed by adding the @MockitoConfig(convertScopes = true)
annotation.
This will convert the @Singleton
bean to an @ApplicationScoped
bean for the test.
これは高度なオプションと見なされ、Bean のスコープを変更することで引き起こされる結果を完全に理解している場合にのみ実行する必要があります。
11.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 | 私たちは、スパイからのモックされた応答を得ることを検証しています。 |
11.2.3. @InjectMock
との併用 @RestClient
The @RegisterRestClient
registers the implementation of the REST Client at runtime, and because the bean needs to be a regular scope, you have to annotate your interface with @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 のインスタンスを使用することを意味していることを示します。 |
11.3. Mocking with Panache
quarkus-hibernate-orm-panache
や quarkus-mongodb-panache
のエクステンションを使っている場合は、Hibernate ORM と Panache モック および MongoDB と Panache モック のドキュメントをチェックして、データアクセスをモックする最も簡単な方法を確認してください。
12. セキュリティーのテスト
Quarkus Securityを使用している場合、アプリケーションのセキュリティー機能を簡単にテストする方法については、セキュリティーのテスト のセクションをご覧ください。
13. Quarkus アプリケーションの開始前にサービスを開始する
非常に一般的なニーズは、Quarkusアプリケーションがテストを開始する前に、Quarkusアプリケーションに依存するいくつかのサービスを開始することです。このニーズに対応するために、Quarkusでは、 @io.quarkus.test.common.QuarkusTestResource
と io.quarkus.test.common.QuarkusTestResourceLifecycleManager
を提供します。
By simply annotating any test in the test suite with @QuarkusTestResource
, Quarkus will run the corresponding QuarkusTestResourceLifecycleManager
before any tests are run.
A test suite is also free to utilize multiple @QuarkusTestResource
annotations, in which case all the corresponding QuarkusTestResourceLifecycleManager
objects will be run before the tests.
テストリソースは、テストクラスまたはカスタムプロファイルで定義されている場合でもグローバルです。つまり、重複を削除しても、すべてのテストですべてアクティブになります。単一のテストクラスまたはテストプロファイルに限定してテストリソースを有効にする場合は、@QuarkusTestResource(restrictToAnnotatedClass = true) を使用できます。
|
When using multiple test resources, they can be started concurrently. For that you need to set @QuarkusTestResource(parallel = true) .
|
Quarkusでは、QuarkusTestResourceLifecycleManager
の実装がいくつか提供されていますが (H2 データベースを起動する io.quarkus.test.h2.H2DatabaseTestResource
や、モック Kubernetes API サーバーを起動する io.quarkus.test.kubernetes.client.KubernetesServerTestResource
を参照)、特定のアプリケーションのニーズに対応するためにカスタム実装を作成するのが一般的です。一般的なケースとしては、Testcontainers を使用した docker コンテナーの起動 (その例は こちら を参照)、Wiremock を使用したモック HTTP サーバの起動 (その例は こちら を参照) などがあります。
As QuarkusTestResourceLifecycleManager is not a CDI Bean, classes that implement it can’t have fields injected with @Inject . You can use String propertyName = ConfigProvider.getConfig().getValue("quarkus.my-config-group.myconfig", String.class);
|
13.1. テストクラスの変更
When creating a custom QuarkusTestResourceLifecycleManager
that needs to inject something into the test class, the inject
methods can be used.
If for example you have a test like the following:
@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 がテストクラスへの必要な注入を実行した後に発生することは言及に値します。 |
13.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. ハング検出
@QuarkusTest
は、予期しないハングを診断するために使用できるハング検出をサポートしています。指定された時間内に進捗がない場合 (つまり、JUnit コールバックが呼び出されない場合)、Quarkus はスタックトレースをコンソールに出力して、ハングの診断を助けます。このタイムアウトのデフォルト値は 10 分です。
これ以上のアクションは実行されず、テストは通常どおり (通常は CI がタイムアウトするまで) 続行されますが、出力されたスタックトレースは、ビルドが失敗した理由を診断するのに役立ちます。このタイムアウトは、quarkus.test.hang-detection-timeout
システムプロパティーで制御できます (これは application.properties でも設定できますが、Quarkus が起動するまで読み取られないため、Quarkus の起動タイムアウトはデフォルトの 10 分です)。
15. ネイティブ実行可能ファイルテスト
@QuarkusIntegrationTest
を使用してネイティブ実行可能ファイルをテストすることも可能です。これは、テストに注入すること (そして、ネイティブ実行可能ファイルは別の非 JVM プロセスで実行されることーこれは実際には可能ではありません) を除いて、このガイドで述べたすべての機能をサポートしています。
これについては、ネイティブ実行可能ファイルガイド で説明されています。
16. @QuarkusIntegrationTest
の使用
@QuarkusIntegrationTest
should be used to launch and test the artifact produced by the Quarkus build, and supports testing a jar (of whichever type), a native image or container image.
Put simply, this means that if the result of a Quarkus build (mvn package
or gradle build
) is a jar, that jar will be launched as java -jar …
and tests run against it.
If instead a native image was built, then the application is launched as ./application …
and again the tests run against the running application.
Finally, if a container image was created during the build (by including the quarkus-container-image-jib
, quarkus-container-image-docker
, or container-image-podman
extensions and having the
quarkus.container-image.build=true
property configured), then a container is created and run (this requires the docker
or podman
executable being present).
これは、同じ機能セットをサポートし、同じ制限を持つブラックボックステストです。
|
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 | 便宜上、以前のテストを拡張していますが、独自のテストを実装することもできます。 |
詳細については、次のリンクを参照してください: building-native-image#testing-the-native-executable[ネイティブ実行可能ファイルのテストのガイド]
|
While adding test-specific configuration properties using |
16.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
でアクティブ化されます。
16.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
To test against a running instance that only accepts SSL/TLS connection (example: https://1.2.3.4:4321
) set the system property quarkus.http.test-ssl-enabled
to true
.
17. @QuarkusTest
と他のタイプのテストを混合
@QuarkusTest
でアノテーションが付けられたテストと、@QuarkusDevModeTest
、@QuarkusProdModeTest
、@QuarkusUnitTest
のいずれかでアノテーションが付けられたテストを、1 回の実行 (たとえば、1 回の Maven Surefire プラグインの実行) で混合することはできません。ただsじょ。後者の 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>
Currently For Maven, this means that the former should be run by the surefire plugin while the latter should be run by the failsafe plugin. For Gradle, this means the two types of tests should belong to different source sets. Source set configuration example
|
18. IDE から @QuarkusTest
を実行する
ほとんどの IDE では、選択したクラスを JUnit テストとして直接実行できるようになっています。そのためには、選択した IDE の設定でいくつかのプロパティーを設定する必要があります:
-
java.util.logging.manager
(ロギングガイド を参照) -
maven.home
(${maven.home}/conf/settings.xml
にカスタム設定がある場合のみ)、Maven Guide を参照) -
maven.settings
(カスタム版のsettings.xml
ファイルをテストに使用する場合)
18.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」設定に回避策が適用されます。
19. 開発サービスのテスト
デフォルトでは、テストは 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 インスタンスの設定、データベースへのデータの追加など)。
アプリケーションをコンテナーとしてランチャーにする |
20. Testing Components
Quarkus provides the QuarkusComponentTestExtension
, a JUnit extension to ease the testing of components and mocking of their dependencies.
This JUnit extension is available in the quarkus-junit5-component
dependency.
Let’s have a component Foo
- a CDI bean with two injection points.
Foo
componentpackage 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 is an @ApplicationScoped CDI bean. |
2 | Foo depends on Charlie which declares a method ping() . |
3 | Foo depends on the config property bar . @Inject is not needed for this injection point because it also declares a CDI qualifier - this is a Quarkus-specific feature. |
Then a component test could look like:
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 | The QuarkusComponentTest annotation registers the JUnit extension. |
2 | Sets a configuration property for the test. |
3 | The test injects the component under the test. The types of all fields annotated with @Inject are considered the component types under test. You can also specify additional component classes via @QuarkusComponentTest#value() . Furthermore, the static nested classes declared on the test class are components too. |
4 | The test also injects a mock for Charlie . Charlie is an unsatisfied dependency for which a synthetic @Singleton bean is registered automatically. The injected reference is an "unconfigured" Mockito mock. |
5 | We can leverage the Mockito API in a test method to configure the behavior. |
You can find more examples and hints in the testing components reference guide.