Java API テスト
API テストは HTTP 経由でアプリを検証します。@ShadowPg が pgmem の起動、アプリの接続設定、テストごとのデータ復元を処理します。既存の DataSource と接続プールはテスト中も維持されます。
HTTP サーバーをランダムなポートで起動する
Section titled “HTTP サーバーをランダムなポートで起動する”以下はアプリに POST /orders と GET /orders がある想定です。db/schema.sql は src/test/resources に配置します。
@ShadowPg(schema = "db/schema.sql")class OrdersApiTest { @LocalServerPort int port; HttpClient http = HttpClient.newHttpClient();
@Test void createsOrderThroughHttp() throws Exception { HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl() + "/orders")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString("{\"sku\":\"book\"}")) .build(); assertEquals(201, http.send(request, HttpResponse.BodyHandlers.ofString()).statusCode()); }
@Test void startsFromPreparedDataAgain() throws Exception { HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl() + "/orders")).build(); var response = http.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode()); assertEquals("[]", response.body()); }
String baseUrl() { return "http://127.0.0.1:" + port; }}@ShadowPg(schema = "db/schema.sql")class OrdersApiTest { @Inject EmbeddedServer app; HttpClient http = HttpClient.newHttpClient();
@Test void createsOrderThroughHttp() throws Exception { HttpRequest request = HttpRequest.newBuilder(app.getURI().resolve("/orders")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString("{\"sku\":\"book\"}")) .build(); assertEquals(201, http.send(request, HttpResponse.BodyHandlers.ofString()).statusCode()); }
@Test void startsFromPreparedDataAgain() throws Exception { HttpRequest request = HttpRequest.newBuilder(app.getURI().resolve("/orders")).build(); var response = http.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode()); assertEquals("[]", response.body()); }}アプリの通常のテスト用依存に加え、Spring Boot は testImplementation("io.github.shibukawa.pgmem:pgmem-spring:1.18.0")、Micronaut は testImplementation("io.github.shibukawa.pgmem:pgmem-micronaut:1.18.0") を追加します。それぞれ io.github.shibukawa.pgmem.spring.ShadowPg または io.github.shibukawa.pgmem.micronaut.ShadowPg をインポートします。このアノテーションがアプリの標準の DataSource 設定に pgmem の URL を渡し、アプリ起動後の状態を復元基準にします。起動時のマイグレーションを使う場合は schema を省略できます。追加の初期データは seed = "db/seed.sql" で指定します。クラスごとに別のフォークとサーバーを持ちます。
PostgreSQL JDBC ドライバーとプラットフォーム用の pgmem-native も、Java の基本設定に従ってテスト依存へ追加します。
その他の構成とデータセット
Section titled “その他の構成とデータセット”DataSource を直接受け取るフレームワークなら、リクエスト受付前に pg.applicationFork().dataSource() でアプリを構築します。別の JVM で動かす場合は、プールを作る前の起動設定へ pg.applicationFork().jdbcUrl() を渡します。同じアプリとフォークを逐次ケースで維持し、@BeforeEach で reset() を呼びます。
スイート固有の基準データが必要なら、アプリ用フォークに一度シードを入れ、Snapshot seeded = pg.applicationFork().snapshot() を作ります。各ケースの前に pg.applicationFork().reset(seeded) で戻し、スイート終了後に seeded を閉じます。
同じ HTTP ライフサイクルをブラウザーで試す場合は E2E テスト を参照してください。
安全な並列実行
Section titled “安全な並列実行”1 つの @ShadowPg Spring Boot・Micronaut アプリを使うメソッドは逐次実行します。HTTP サーバー、Pool、フォークを共有し、各メソッドの前に同じフォークを reset するためです。異なるクラスは、それぞれ別のアプリコンテキスト、pgmem フォーク、HTTP ポートを持つ場合に並列化します。src/test/resources/junit-platform.properties ではクラスだけを並列化できます。
junit.jupiter.execution.parallel.enabled = truejunit.jupiter.execution.parallel.mode.default = same_threadjunit.jupiter.execution.parallel.mode.classes.default = concurrentstatic なアプリや DataSource をクラス間で共有しないでください。次の reset 前にリクエストと DB を使うバックグラウンド処理を終え、トランザクションを閉じます。全メソッドを並列化するなら、クラス単位のアノテーション方式ではなくメソッドごとに別のアプリとフォークを作ります。