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

コンポーネントのテスト

Quarkus のコンポーネントモデルは CDI 上に構築されています。 そのため、Quarkus は、コンポーネント/CDI Bean を簡単にテストし、それらの依存関係をモック化できる JUnit エクステンションである QuarkusComponentTestExtension を提供します。 @QuarkusTest とは異なり、このエクステンションは完全な Quarkus アプリケーションを起動せず、CDI コンテナーと設定サービスのみを起動します。 詳細は、ライフサイクル セクションを参照してください。

このエクステンションは、 quarkus-junit5-component 依存関係で利用できます。

1. 基本例

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

また、 QuarkusComponentTestExtension は、テストメソッドのパラメーターを解決し、一致する Bean を注入します。

したがって、上記のコードスニペットは次のように書き直すことができます。

テストメソッドパラメーターを使用したシンプルなコンポーネントテスト
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
@TestConfigProperty(key = "bar", value = "true")
public class FooTest {

    @Test
    public void testPing(Foo foo, @InjectMock Charlie charlieMock) { (1)
        Mockito.when(charlieMock.ping()).thenReturn("OK");
        assertEquals("OK", foo.ping());
    }
}
1 @io.quarkus.test.component.SkipInject でアノテーションが付けられたパラメーターは、このエクステンションによって解決されることはありません。

さらに、 QuarkusComponentTestExtension 設定を完全に制御する必要がある場合は、 @RegisterExtension アノテーションを使用して、プログラムによってエクステンションを設定できます。

元のテストを次のように書き直すことができます。

プログラムによる設定を使用したシンプルなコンポーネントテスト
import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import io.quarkus.test.InjectMock;
import io.quarkus.test.component.QuarkusComponentTestExtension;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

public class FooTest {

    @RegisterExtension (1)
    static final QuarkusComponentTestExtension extension = QuarkusComponentTestExtension.builder().configProperty("bar","true").build();

    @Inject
    Foo foo;

    @InjectMock
    Charlie charlieMock;

    @Test
    public void testPing() {
        Mockito.when(charlieMock.ping()).thenReturn("OK");
        assertEquals("OK", foo.ping());
    }
}
1 QuarkusComponentTestExtension は、テストクラスの静的フィールドで設定します。

2. ライフサイクル

では、 QuarkusComponentTest は実際には何を実行するのでしょうか。 これは、CDI コンテナーを起動し、専用の 設定オブジェクト を登録します。

テストインスタンスのライフサイクルが Lifecycle#PER_METHOD (デフォルト) の場合、コンテナーは before each テストフェーズ中に起動し、 after each テストフェーズ中に停止します。 一方、テストインスタンスのライフサイクルが Lifecycle#PER_CLASS の場合、コンテナーは before all テストフェーズ中に起動し、 after all テストフェーズ中に停止します。

@Inject および @InjectMock でアノテーションが付けられたフィールドは、テストインスタンスが作成された後に注入されます。 一致する Bean が存在するテストメソッドのパラメーターは、テストメソッドの実行時に解決されます (@io.quarkus.test.component.SkipInject または @org.mockito.Mock でアノテーションが付けられている場合を除く)。 最後に、CDI リクエストコンテキストが各テストメソッドごとにアクティブ化され、終了します。

3. インジェクション

@jakarta.inject.Inject および @io.quarkus.test.InjectMock でアノテーションが付けられたテストクラスのフィールドは、テストインスタンスが作成された後に注入されます。 さらに、一致する Bean が存在するテストメソッドのパラメーターが解決されます (@io.quarkus.test.component.SkipInject または @org.mockito.Mock でアノテーションが付けられている場合を除く)。 また、 RepetitionInfoTestInfo など、自動的にスキップされる JUnit 組み込みパラメーターもいくつかあります。

@Inject インジェクションポイントは、CDI Bean のコンテキストインスタンス (テスト対象の実際のコンポーネント) を受け取ります。 @InjectMock インジェクションポイントは、満たされていない依存関係に対して自動的に作成 (unsatisfied dependency automatically を参照) された "未設定" の Mockito モックを受け取ります。

フィールドに注入された依存 Bean とテストメソッド引数は、それぞれテストインスタンスが破棄される前とテストメソッドが完了した後に正しく破棄されます。

ArgumentsProvider によって提供される @ParameterizedTest メソッドの引数 (たとえば @org.junit.jupiter.params.provider.ValueArgumentsProvider) には、 @SkipInject アノテーションを付ける必要があります。

3.1. テスト対象コンポーネント

テスト対象コンポーネントの初期セットは、テストクラスから派生します。

  1. @jakarta.inject.Inject でアノテーションが付けられたすべてのフィールドの型は、コンポーネント型と見なされます。

  2. @InjectMock@SkipInject、または @org.mockito.Mock でアノテーションが付けられていないテストメソッドパラメーターの型も、コンポーネント型と見なされます。

  3. @QuarkusComponentTest#addNestedClassesAsComponents()true (デフォルト) に設定されている場合、テストクラスで宣言されたすべてのネストされた静的クラスもコンポーネントになります。

@Inject Instance<T> および `@Inject @All List<T>`インジェクションポイントは、特別に処理されます。実際の型引数はコンポーネントとして登録されます。ただし、型引数がインターフェイスの場合、実装は自動的に 登録されません

追加のコンポーネントクラスは、 @QuarkusComponentTest#value() または QuarkusComponentTestExtensionBuilder#addComponentClasses() を使用して設定できます。

3.2. 満たされていない依存関係の自動モック化

通常の CDI 環境とは異なり、コンポーネントが満たされていない依存関係を注入してもテストは失敗しません。 代わりに、満たされていない依存関係に解決されるインジェクションポイントの必要なタイプと修飾子の組み合わせごとに、合成 Bean が自動的に登録されます。 Bean には @Singleton スコープがあるため、Bean は必要な同じタイプと修飾子を持つすべてのインジェクションポイント間で共有されます。 注入される参照は 未設定 の Mockito モックです。 io.quarkus.test.InjectMock アノテーションを使用してテストにモックを注入し、Mockito API を活用して動作を設定できます。

@InjectMock は、Mockito JUnit エクステンションによって提供される機能すべてに代わるものではありません。 これは、CDI Bean の満たされていない依存関係の設定に使用することを目的としたものです。 QuarkusComponentTestMockitoExtension と並行して使用できます。

import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
@QuarkusComponentTest
public class FooTest {

    @TestConfigProperty(key = "bar", value = "true")
    @Test
    public void testPing(Foo foo, @InjectMock Charlie charlieMock, @Mock Ping ping) {
        Mockito.when(ping.pong()).thenReturn("OK");
        Mockito.when(charlieMock.ping()).thenReturn(ping);
        assertEquals("OK", foo.ping());
    }
}

3.3. 満たされていない依存関係のカスタムモック

場合によっては、Bean 属性を完全に制御し、デフォルトのモック動作を設定する必要があることもあります。 QuarkusComponentTestExtensionBuilder#mock() メソッドを介して、モックコンフィギュレーター API を使用できます。

4. 設定

@io.quarkus.test.component.TestConfigProperty アノテーションまたは QuarkusComponentTestExtensionBuilder#configProperty(String, String) メソッドを使用して、テストの設定プロパティーを設定できます。 不足している設定プロパティーに対してデフォルト値を使用する必要があるだけの場合は、 @QuarkusComponentTest#useDefaultConfigProperties() または QuarkusComponentTestExtensionBuilder#useDefaultConfigProperties() が役立つ場合があります。

@io.quarkus.test.component.TestConfigProperty アノテーションを使用して、テストメソッドの設定プロパティーを設定することもできます。 ただし、テストインスタンスのライフサイクルが Lifecycle#_PER_CLASS の場合、このアノテーションはテストクラスでのみ使用でき、テストメソッドでは無視されます。

また、CDI Bean は、注入されたすべての Config Mappings に対して自動的に登録されます。マッピングには、テスト設定プロパティーが入力されます。

5. CDI インターセプターのモック化

テスト対象のコンポーネントクラスがインターセプターバインディングを宣言している場合は、インターセプションもモック化する必要があるかもしれません。 このタスクを達成するには 2 つの方法があります。 まず、テストクラスのネストされた静的クラスとしてインターセプタークラスを定義できます。

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import io.quarkus.test.component.QuarkusComponentTest;
import org.junit.jupiter.api.Test;

@QuarkusComponentTest
public class FooTest {

    @Inject
    Foo foo;

    @Test
    public void testPing() {
        assertEquals("OK", foo.ping());
    }

    @ApplicationScoped
    static class Foo {

       @SimpleBinding (1)
       String ping() {
         return "ok";
       }

    }

    @SimpleBinding
    @Interceptor
    static class SimpleInterceptor { (2)

        @AroundInvoke
        Object aroundInvoke(InvocationContext context) throws Exception {
            return context.proceed().toString().toUpperCase();
        }

    }
}
1 @SimpleBinding はインターセプターバインディングです。
2 インターセプタークラスは自動的にテスト対象コンポーネントと見なされます。
@QuarkusComponentTest でアノテーションが付けられたテストクラスで宣言された静的ネストクラスは、意図しない CDI 競合を防ぐために、 @QuarkusTest の実行時に Bean 検出から除外されます。

もう 1 つの方法は、テストクラスでインターセプターメソッドを直接宣言することです。この場合、メソッドが関連するインターセプションフェーズで呼び出されます。

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import io.quarkus.test.component.QuarkusComponentTest;
import org.junit.jupiter.api.Test;

@QuarkusComponentTest
public class FooTest {

    @Inject
    Foo foo;

    @Test
    public void testPing() {
        assertEquals("OK", foo.ping());
    }

    @SimpleBinding (1)
    @AroundInvoke (2)
    Object aroundInvoke(InvocationContext context) throws Exception {
       return context.proceed().toString().toUpperCase();
    }

    @ApplicationScoped
    static class Foo {

       @SimpleBinding (1)
       String ping() {
         return "ok";
       }

    }
}
1 結果として得られるインターセプターのインターセプターバインディングは、メソッドにインターセプターバインディングタイプをアノテーション付けすることによって指定されます。
2 インターセプトの種類を定義します。

関連コンテンツ