The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

アプリケーションのテスト

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 つのテスト依存関係が表示されます。

Maven
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>
Gradle
dependencies {
    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")
}

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 をリッスンします。 これにより、アプリケーションを並行して実行しながらテストを実行することができます。

テストポートの変更

application.propertiesquarkus.http.test-port を設定することで、を HTTP 用にテストで使われるポートを設定出来、 quarkus.http.test-ssl-port を設定することで HTTPS 用にテストで使用するポートを設定することが出来ます。

quarkus.http.test-port=8083
quarkus.http.test-ssl-port=8446

0 を使用すると、(オペレーティングシステムによって割り当てられた)ランダムなポートが使用されることになります。

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-teststrue とすることで、これらのコールバックを @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
テストクラスやメソッドからアノテーションを読み込んで、コールバックが何をするかを制御することができます。
BeforeEachCallback のような JUnit Jupiter コールバックインターフェースを使用することは可能ですが、Quarkus では JUnit が認識していないカスタムクラスローダーでテストを実行する必要があるため、クラスロードの問題が発生する可能性があります。

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 アノテーションを持つテストに制限できます。これは、 QuarkusTestProfiletags メソッドを 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 の値と一致するため、 SingleTagTestMultipleTagsTest が実行されます。

  • 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には依存しないことに注意してください。好きなモッキングライブラリを使うことができますし、必要な動作を提供するためにオブジェクトを手動でオーバーライドすることもできます。

@Inject を使用すると、インストールしたモックインスタンスへの CDI プロキシーが取得されますが、これは Mockito.verify などのメソッドに渡すのには適していません。 これらのメソッドはモックインスタンスそのものを要求するためです。 したがって、 verify などのメソッドを呼び出す必要がある場合は、テストでモックインスタンスを保持するか、 @io.quarkus.test.InjectMock を使用する必要があります。

11.2.1. @InjectMock での更なる単純化

QuarkusMock が提供する機能を基に構築されている Quarkus では、ユーザーが Mockito を簡単に利用して、 QuarkusMock でサポートされている Bean をモックすることもできます。

この機能は、 quarkus-junit5-mockito 依存関係が存在する場合にのみ、 @io.quarkus.test.InjectMock アノテーションで利用できます。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5-mockito</artifactId>
    <scope>test</scope>
</dependency>

@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 のスコープを変更することで引き起こされる結果を完全に理解している場合にのみ実行する必要があります。

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 "を返すように指示しています。 GreetingResourceGreetingService に挨拶を要求すると、通常の GreetingService Bean の応答の代わりに、モックされた応答が返ってきます。スパイをスタブするために when(Object) を使用することが不可能であったり、現実的でなかったりすることがあります。したがって、spyを使用する場合は、スタブ用のメソッドである doReturn|Answer|Throw() ファミリーを検討してください。
3 私たちは、スパイからのモックされた応答を得ることを検証しています。

11.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 のインスタンスを使用することを意味していることを示します。

11.3. Mocking with Panache

quarkus-hibernate-orm-panachequarkus-mongodb-panache のエクステンションを使っている場合は、Hibernate ORM with Panache モック および MongoDB with Panache モックのドキュメントをチェックして、データアクセスをモックする最も簡単な方法を確認してください。

12. セキュリティーのテスト

Quarkus Securityを使用している場合、アプリケーションのセキュリティー機能を簡単にテストする方法については、セキュリティーのテスト のセクションをご覧ください。

13. Quarkus アプリケーションの開始前にサービスを開始する

非常に一般的なニーズとして、Quarkus アプリケーションをテスト用に起動する前に、Quarkus アプリケーションが依存するいくつかのサービスを起動することが挙げられます。このニーズに対応するために、Quarkusでは、 @io.quarkus.test.common.QuarkusTestResourceio.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); を使用できます。

13.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 メソッドに示すように、 MyWireMockResourcewireMockServer フィールドを注入させることができます:

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();
    }
}

13.3. @WithTestResource の使用法

@QuarkusTestResource によって提供されるテストリソースは、グローバルに使用できるか、またはアノテーション付きテストクラス (restrictToAnnotatedClass) に制限されますが、アノテーション @WithTestResource を使用すると、実行用にテストリソース別にテストをさらにグループ化できます。 @WithTestResource には、 TestResourceScope 列挙値を取る scope プロパティーがあります。

  • TestResourceScope.MATCHING_RESOURCES (デフォルト): Quarkus は同じテストリソースを持つテストをグループ化し、まとめてに実行します。グループが実行されると、すべてのテストリソースが停止され、次のグループが実行されます。

  • TestResourceScope.RESTRICTED_TO_CLASS: テストリソースは、アノテーションが付けられたテストクラスでのみ使用でき、テストクラスが実行されると停止されます。

  • TestResourceScope.GLOBAL: テストリソースはテストスイート内のすべてのテストに適用されます

次のいずれかに該当する場合は、Quarkus を再起動する必要があります。

  • 現在のテストのテストリソースの少なくとも 1 つが、テストクラスに制限されている

  • 次のテストのテストリソースの少なくとも 1 つが、テストクラスに制限されている

  • 異なる MATCHING_RESOURCES スコープのテストリソースが使用されている

14. ハング検出

@QuarkusTest は、予期しないハングを診断するために使用できるハング検出をサポートしています。指定された時間内に進捗がない場合 (つまり、JUnit コールバックが呼び出されない場合)、Quarkus はスタックトレースをコンソールに出力して、ハングの診断を助けます。このタイムアウトのデフォルト値は 10 分です。

これ以上のアクションは実行されず、テストは通常どおり (通常は CI がタイムアウトするまで) 続行されますが、出力されたスタックトレースは、ビルドが失敗した理由を診断するのに役立ちます。このタイムアウトは、 quarkus.test.hang-detection-timeout システムプロパティーで制御できます (これは application.properties でも設定できますが、Quarkus が起動するまで読み取られないため、Quarkus の起動タイムアウトはデフォルトの 10 分です)。

15. ネイティブ実行可能ファイルテスト

@QuarkusIntegrationTest を使用してネイティブ実行可能ファイルをテストすることも可能です。これは、テストに注入すること (そして、ネイティブ実行可能ファイルは別の非 JVM プロセスで実行されることーこれは実際には可能ではありません) を除いて、このガイドで述べたすべての機能をサポートしています。

これについては、ネイティブ実行可能ファイルガイド で説明されています。

16. @QuarkusIntegrationTest の使用

@QuarkusIntegrationTest は、Quarkus ビルドによって生成されたアーティファクトを起動およびテストするために使用する必要があり、jar (タイプを問わず)、ネイティブイメージ、またはコンテナーイメージのテストをサポートします。 簡単に言えば、Quarkus ビルド (mvn package または gradle build) の結果が jar である場合、その jar は java -jar …​ として起動され、それに対して実行がテストされることを意味します。 代わりにネイティブイメージがビルドされた場合、アプリケーションは ./application …​ として起動され、実行中のアプリケーションに対して実行がテストされます。 最後に、ビルド中に (quarkus-container-image-jibquarkus-container-image-docker または container-image-podman エクステンションを含め、 quarkus.container-image.build=true プロパティーを設定することで) コンテナーイメージが作成された場合、コンテナーが作成されて実行されます (これには、 docker または podman の実行可能ファイルが存在する必要があります)。

これは、同じ機能セットをサポートし、同じ制限を持つブラックボックステストです。

@QuarkusIntegrationTest でアノテーションが付けられたテストはビルドの結果をテストするため、統合テストスイートの一部として実行する必要があります。 - 例えば、Maven を使用している場合は -DskipITs=false を設定し、Gradle を 使用している場合は quarkusIntTest タスクを設定します。これらのテストは、Quarkus がまだ最終のアーティファクトを作成していないため、 @QuarkusTest と同じフェーズで実行すると 機能しません

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.

@QuarkusIntegrationTest を使ってアプリケーションをテストすると、 prod の設定プロファイルを使って起動しますが、これは quarkus.test.integration-test-profile プロパティを使って変更することができます。

src/test/resources/application.properties を使用してテスト固有の設定プロパティーを追加する際 (main ではなく test があることに注意)、 ユニットテストは可能ですが、インテグレーションテストは不可能です。

16.1. コンテナーの起動

@QuarkusIntegrationTest によってコンテナーが起動されると (quarkus.container-image.buildtrue に設定されているためにアプリケーションがビルドされたため)、コンテナーは予測可能なコンテナーネットワーク上で起動されます。これにより、アプリケーションをサポートするためにサービスを起動する必要があるインテグレーションテストの作成が容易になります。 これは、 @QuarkusIntegrationTest が、Dev Services を介して起動されたコンテナーでそのまま機能することを意味しますが、追加のコンテナーを起動する QuarkusTestLifecycleManager リソースを使用して有効化することも意味します。 これは、 QuarkusTestLifecycleManagerio.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

SSL/TLS 接続のみを受け入れる実行中のインスタンス (例: https://1.2.3.4:4321) に対してテストするには、システムプロパティー quarkus.http.test-ssl-enabledtrue に設定します。

17. @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>

現在、 @QuarkusTest@QuarkusIntegrationTest を同じテストランで実行しないでください。

Maven の場合、前者は surefire プラグインによって、後者は failsafe プラグインによって実行される必要があることを意味します。

Gradle の場合、これは 2 種類のテストが異なるソースセットに属する必要があることを意味します。

ソースセットの設定例
/** custom source sets
 *
 * io.quarkus.gradle.QuarkusPlugin
 * io.quarkus.test.junit.IntegrationTestUtil
 *
 * to work around
 *  https://github.com/quarkusio/quarkus/issues/43796
 *  https://github.com/quarkusio/quarkus/issues/43804
 */

sourceSets {
    create("intTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
    create("e2eTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}

configurations {
    getByName("intTestImplementation") {
        extendsFrom(configurations.testImplementation.get())
    }
    getByName("intTestRuntimeOnly") {
        extendsFrom(configurations.testRuntimeOnly.get())
    }
    getByName("e2eTestImplementation") {
        extendsFrom(configurations.testImplementation.get())
    }
    getByName("e2eTestRuntimeOnly") {
        extendsFrom(configurations.testRuntimeOnly.get())
    }
}

tasks.register<Test>("intTest") {
    group = "verification"
    description = "Runs integration tests"

    testClassesDirs = sourceSets["intTest"].output.classesDirs
    classpath = sourceSets["intTest"].runtimeClasspath

    systemProperty("build.output.directory", "build")
    systemProperty("quarkus.profile", "intTest")
}

tasks.register<Test>("e2eTest") {
    group = "verification"
    description = "Runs e2e tests"

    val quarkusBuild = tasks.getByName("quarkusBuild")
    dependsOn(quarkusBuild)

    testClassesDirs = sourceSets["e2eTest"].output.classesDirs
    classpath = sourceSets["e2eTest"].runtimeClasspath

    systemProperty("build.output.directory", "build")
}

idea.module {
    testSources.from(sourceSets["intTest"].kotlin.srcDirs)
    testSources.from(sourceSets["e2eTest"].kotlin.srcDirs)
}

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」設定に回避策が適用されます。

18.2. VSCode "run with" 設定

プロジェクトディレクトリーやワークスペースのルートにある settings.json には、テスト設定で次の回避策が必要です:

"java.test.config": [
    {
        "name": "quarkusConfiguration",
        "vmargs": [ "-Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dmaven.home=<path-to-your-maven-installation> ..." ],
        ...
    },
  ...
]

18.3. IntelliJ IDEA JUnit テンプレート

IntelliJ IDEA では何も必要ありません。なぜなら、IDEは systemPropertyVariablespom.xml の surefire プラグイン設定から取得するからです。

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 インスタンスの設定、データベースへのデータの追加など)。

アプリケーションをコンテナーとしてランチャーにする @QuarkusIntegrationTest テストの場合、 io.quarkus.test.common.DevServicesContext は、アプリケーションコンテナーが起動されたコンテナーネットワークの ID へのアクセスも提供します (containerNetworkId メソッドを使用)。これは、アプリケーションが通信する追加のコンテナーを起動する必要がある QuarkusTestResourceLifecycleManager で使用できます。

20. コンポーネントのテスト

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 を活用して動作を設定できます。

さらに多くの例とヒントについては、 テストコンポーネントリファレンスガイド を参照してください。

関連コンテンツ