Quarkusアプリケーションのテスト
1. 概要
Quarkusを使用すると、堅牢でクリーンなアプリケーションの開発が非常に簡単になります。 しかし、テストはどうですか?
このチュートリアルでは、Quarkusアプリケーションをテストする方法を詳しく見ていきます。 Quarkusが提供するテストの可能性を探り、依存関係の管理と挿入、モッキング、プロファイル構成などの概念、およびQuarkusアノテーションやネイティブ実行可能ファイルのテストなどのより具体的な概念について説明します。
2. 設定
以前のQuarkusIOのガイドで構成された基本的なQuarkusプロジェクトから始めましょう。
まず、 quarkus-reasteasy-jackson 、 quarkus-hibernate-orm-panache 、 quarkus-jdbc-h2 、quarkus-を追加します。 junit5-mockito 、および quarkus-test-h2 Mavenの依存関係:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
</dependency>
次に、ドメインエンティティを作成しましょう。
public class Book extends PanacheEntity {
private String title;
private String author;
}
引き続き、本を検索する方法を使用して、単純なPanacheリポジトリを追加します。
public class BookRepository implements PanacheRepository {
public Stream<Book> findBy(String query) {
return find("author like :query or title like :query", with("query", "%"+query+"%")).stream();
}
}
それでは、 LibraryService を記述して、ビジネスロジックを保持しましょう。
public class LibraryService {
public Set<Book> find(String query) {
if (query == null) {
return bookRepository.findAll().stream().collect(toSet());
}
return bookRepository.findBy(query).collect(toSet());
}
}
最後に、 LibraryResource を作成して、HTTPを介してサービス機能を公開しましょう。
@Path("/library")
public class LibraryResource {
@GET
@Path("/book")
public Set findBooks(@QueryParam("query") String query) {
return libraryService.find(query);
}
}
3. @Alternativeの実装
テストを作成する前に、リポジトリに本があることを確認しましょう。 Quarkusを使用すると、 CDI @Alternativeメカニズムを使用して、テスト用のカスタムBean実装を提供できます。 BookRepositoryを拡張するTestBookRepositoryを作成しましょう。
@Priority(1)
@Alternative
@ApplicationScoped
public class TestBookRepository extends BookRepository {
@PostConstruct
public void init() {
persist(new Book("Dune", "Frank Herbert"),
new Book("Foundation", "Isaac Asimov"));
}
}
この代替beanをテストパッケージに配置します。@Priority(1)および @Alternative アノテーションがあるため、テストが確実に行われます。実際のBookRepository実装よりも優先されます。 このは、すべてのQuarkusテストが使用できるグローバルモックを提供できる1つの方法です。 焦点を絞ったモックについては後ほど説明しますが、それでは、最初のテストの作成に移りましょう。
4. HTTP統合テスト
簡単なRESTで保証された統合テストを作成することから始めましょう。
@QuarkusTest
class LibraryResourceIntegrationTest {
@Test
void whenGetBooksByTitle_thenBookShouldBeFound() {
given().contentType(ContentType.JSON).param("query", "Dune")
.when().get("/library/book")
.then().statusCode(200)
.body("size()", is(1))
.body("title", hasItem("Dune"))
.body("author", hasItem("Frank Herbert"));
}
}
@QuarkusTestアノテーションが付けられたこのテストは、最初にQuarkusアプリケーションを起動し、次にリソースのエンドポイントに対して一連のHTTPリクエストを実行します。
それでは、いくつかのQuarkusメカニズムを利用して、テストをさらに改善してみましょう。
4.1. @TestHTTPResourceによるURLインジェクション
HTTPエンドポイントのパスをハードコーディングする代わりに、リソースURLを挿入しましょう。
@TestHTTPResource("/library/book")
URL libraryEndpoint;
そして、それを私たちのリクエストで使用しましょう:
given().param("query", "Dune")
.when().get(libraryEndpoint)
.then().statusCode(200);
または、Rest-assuredを使用せずに、挿入されたURLへの接続を開いて、応答をテストしてみましょう。
@Test
void whenGetBooks_thenBooksShouldBeFound() throws IOException {
assertTrue(IOUtils.toString(libraryEndpoint.openStream(), defaultCharset()).contains("Asimov"));
}
ご覧のとおり、 @TestHTTPResource URLインジェクションは、エンドポイントにアクセスするための簡単で柔軟な方法を提供します。
4.2. @TestHTTPEndpoint
これをさらに進めて、Quarkusが提供する@TestHTTPEndpointアノテーションを使用してエンドポイントを構成しましょう。
@TestHTTPEndpoint(LibraryResource.class)
@TestHTTPResource("book")
URL libraryEndpoint;
このように、 LibraryResource のパスを変更することにした場合、テストはそれに触れることなく正しいパスを取得します。
@TestHTTPEndpointはクラスレベルでも適用できます。この場合、REST-assuredは、すべてのリクエストの前にLibraryResourceのPathを自動的に付加します。
@QuarkusTest
@TestHTTPEndpoint(LibraryResource.class)
class LibraryHttpEndpointIntegrationTest {
@Test
void whenGetBooks_thenShouldReturnSuccessfully() {
given().contentType(ContentType.JSON)
.when().get("book")
.then().statusCode(200);
}
}
5. コンテキストと依存性の注入
依存性注入に関しては、 Quarkusテストでは、必要な依存性に@Injectを使用できます。 LibraryService のテストを作成して、これが実際に動作することを確認しましょう。
@QuarkusTest
class LibraryServiceIntegrationTest {
@Inject
LibraryService libraryService;
@Test
void whenFindByAuthor_thenBookShouldBeFound() {
assertFalse(libraryService.find("Frank Herbert").isEmpty());
}
}
それでは、Panache BookRepositoryをテストしてみましょう。
class BookRepositoryIntegrationTest {
@Inject
BookRepository bookRepository;
@Test
void givenBookInRepository_whenFindByAuthor_thenShouldReturnBookFromRepository() {
assertTrue(bookRepository.findBy("Herbert").findAny().isPresent());
}
}
しかし、テストを実行すると失敗します。 これは、がトランザクションのコンテキスト内で実行する必要があり、アクティブなものがないためです。 これは、テストクラスに@Transactionalを追加するだけで修正できます。 または、必要に応じて、両方をバンドルする独自のステレオタイプを定義できます @QuarkusTest と
@QuarkusTest
@Stereotype
@Transactional
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface QuarkusTransactionalTest {
}
それでは、それをテストに適用しましょう。
@QuarkusTransactionalTest
class BookRepositoryIntegrationTest
ご覧のとおり、Quarkusテストは完全なCDIBean であるため、依存性注入、トランザクションコンテキスト、CDIインターセプターなどのCDIのすべての利点を活用できます。
6. 嘲笑
モッキングは、テスト作業の重要な側面です。 上ですでに見たように、QuarkusテストはCDI @Alternativeメカニズムを利用できます。 ここで、Quarkusが提供するモック機能について詳しく見ていきましょう。
6.1. @Mock
@Alternativeアプローチを少し簡略化したものとして、@Mockステレオタイプアノテーションを使用できます。 これは、 @Alternativeおよび@Primary(1)アノテーションをバンドルします。
6.2. @QuarkusMock
グローバルに定義されたモックを作成したくないが、 1つのテストの範囲内でのみモックを作成したい場合は、@QuarkusMockを使用できます。
@QuarkusTest
class LibraryServiceQuarkusMockUnitTest {
@Inject
LibraryService libraryService;
@BeforeEach
void setUp() {
BookRepository mock = Mockito.mock(TestBookRepository.class);
Mockito.when(mock.findBy("Asimov"))
.thenReturn(Arrays.stream(new Book[] {
new Book("Foundation", "Isaac Asimov"),
new Book("I Robot", "Isaac Asimov")}));
QuarkusMock.installMockForType(mock, BookRepository.class);
}
@Test
void whenFindByAuthor_thenBooksShouldBeFound() {
assertEquals(2, libraryService.find("Asimov").size());
}
}
6.3. @InjectMock
少し単純化して、@QuarkusMockの代わりにQuarkus@InjectMockアノテーションを使用しましょう。
@QuarkusTest
class LibraryServiceInjectMockUnitTest {
@Inject
LibraryService libraryService;
@InjectMock
BookRepository bookRepository;
@BeforeEach
void setUp() {
when(bookRepository.findBy("Frank Herbert"))
.thenReturn(Arrays.stream(new Book[] {
new Book("Dune", "Frank Herbert"),
new Book("Children of Dune", "Frank Herbert")}));
}
@Test
void whenFindByAuthor_thenBooksShouldBeFound() {
assertEquals(2, libraryService.find("Frank Herbert").size());
}
}
6.4. @InjectSpy
スパイのみに関心があり、beanの動作を置き換えない場合は、提供されている@InjectSpyアノテーションを使用できます。
@QuarkusTest
class LibraryResourceInjectSpyIntegrationTest {
@InjectSpy
LibraryService libraryService;
@Test
void whenGetBooksByAuthor_thenBookShouldBeFound() {
given().contentType(ContentType.JSON).param("query", "Asimov")
.when().get("/library/book")
.then().statusCode(200);
verify(libraryService).find("Asimov");
}
}
7. テストプロファイル
でさまざまな構成でテストを実行したい場合があります。 このために、Quarkusはテストプロファイルの概念を提供します。 BookRepository のカスタマイズされたバージョンを使用して、別のデータベースエンジンに対して実行されるテストを作成しましょう。また、既に構成されているパスとは異なるパスでHTTPリソースを公開します。
このために、QuarkusTestProfileを実装することから始めます。
public class CustomTestProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Collections.singletonMap("quarkus.resteasy.path", "/custom");
}
@Override
public Set<Class<?>> getEnabledAlternatives() {
return Collections.singleton(TestBookRepository.class);
}
@Override
public String getConfigProfile() {
return "custom-profile";
}
}
次に、H2ストレージをメモリからファイルに変更する custom-profile 構成プロパティを追加して、application.propertiesを構成しましょう。
%custom-profile.quarkus.datasource.jdbc.url = jdbc:h2:file:./testdb
最後に、すべてのリソースと構成が整ったら、テストを作成しましょう。
@QuarkusTest
@TestProfile(CustomBookRepositoryProfile.class)
class CustomLibraryResourceManualTest {
public static final String BOOKSTORE_ENDPOINT = "/custom/library/book";
@Test
void whenGetBooksGivenNoQuery_thenAllBooksShouldBeReturned() {
given().contentType(ContentType.JSON)
.when().get(BOOKSTORE_ENDPOINT)
.then().statusCode(200)
.body("size()", is(2))
.body("title", hasItems("Foundation", "Dune"));
}
}
からわかるように @TestProfile 注釈、このテストでは CustomTestProfile
注意すべき点の1つは、 Quarkusがシャットダウンしてから、このテストが実行される前に新しいプロファイルで再起動することです。 これにより、シャットダウン/再起動が発生するまでに時間がかかりますが、柔軟性を高めるために支払う必要があります。
8. ネイティブ実行可能ファイルのテスト
Quarkusは、ネイティブ実行可能ファイルをテストする可能性を提供します。 ネイティブ画像テストを作成しましょう:
@NativeImageTest
@QuarkusTestResource(H2DatabaseTestResource.class)
class NativeLibraryResourceIT extends LibraryHttpEndpointIntegrationTest {
}
そして今、実行することによって:
mvn verify -Pnative
ビルドされているネイティブイメージと、それに対して実行されているテストが表示されます。
@NativeImageTest アノテーションは、Quarkusにネイティブイメージに対してこのテストを実行するように指示しますが、 @QuarkusTestResource は、テストが開始する前にH2インスタンスを別のプロセスに開始します。 後者は、データベースエンジンがネイティブイメージに埋め込まれていないため、ネイティブ実行可能ファイルに対してテストを実行するために必要です。
@ QuarkusTestResource アノテーションを使用して、たとえばTestcontainersなどのカスタムサービスを開始することもできます。 QuarkusTestResourceLifecycleManager インターフェイスを実装し、テストに次の注釈を付けるだけです。
@QuarkusTestResource(OurCustomResourceImpl.class)
ネイティブイメージを構築するには、GraalVMが必要です。
また、現時点では、インジェクションはネイティブ画像テストでは機能しないことに注意してください。 ネイティブに実行されるのはQuarkusアプリケーションだけであり、テスト自体ではありません。
9. 結論
この記事では、Quarkusがアプリケーションのテストに優れたサポートを提供する方法を説明しました。 依存関係の管理、インジェクション、モックなどの単純なものから、構成プロファイルやネイティブイメージなどのより複雑な側面まで、Quarkusは強力でクリーンなテストを作成するための多くのツールを提供します。
いつものように、完全なコードはGitHubでから入手できます。