第4章 Camel Quarkus でのルートのテスト
4.1. Camel Quarkus エクステンションのテスト
テストは、Camel ルートが長期にわたって期待どおりに動作することを確認するための良い方法です。まだの場合は、「Camel Quarkus User guide」の First Steps セクションと Quarkus ドキュメントの Testing your application セクションをご一読ください。
Quarkus のコンテキストでルートをテストする場合、ローカル統合テストを作成することが推奨されます。これには、JVM モードとネイティブモードの両方をカバーできるという利点があります。
JVM モードでは、CamelTestSupport
スタイルのテスト を使用できます。
4.1.1. JVM モードでの実行
JVM モードでは、@QuarkusTest
アノテーションを使用して Quarkus をブートストラップし、@Test
ロジックが実行される 前 に Camel ルートを起動します。
以下に例を示します。
import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; @QuarkusTest class MyTest { @Test public void test() { // Use any suitable code that sends test data to the route and then assert outcomes ... } }
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@QuarkusTest
class MyTest {
@Test
public void test() {
// Use any suitable code that sends test data to the route and then assert outcomes
...
}
}
サンプル実装は Camel Quarkus ソースにあります。
4.1.2. ネイティブモードでの実行
サポートされているすべてのエクステンションに対して、アプリケーションがネイティブモードで動作することを常にテストしてください。
それぞれの JVM モードクラスからロジックを継承することにより、JVM モード用に定義されたテストロジックを再利用できます。
@QuarkusIntegrationTest
アノテーションを追加して、Quarkus JUnit エクステンションに、テスト対象のアプリケーションをネイティブイメージにコンパイルし、テストを実行する前にイメージを起動するように指示します。
import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest class MyIT extends MyTest { ... }
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
class MyIT extends MyTest {
...
}
サンプル実装は Camel Quarkus ソースにあります。
4.1.3. @QuarkusTest
と @QuarkusIntegrationTest
の違い
ネイティブ実行可能ファイルは、バイトコードではなくネイティブコードであるため、実行に JVM を必要とせず、また JVM で実行することもできません。
従来の JVM を使用して実行するために、テストをネイティブコードにコンパイルしても意味はありません。
そのため、テストとアプリケーション間の通信は、ネットワーク (HTTP/REST、またはアプリケーションが使用するその他のプロトコル)、ファイルシステム (ログファイルなど) の監視、またはその他のプロセス間通信を介して行う必要があります。
4.1.3.1. JVM モードでの @QuarkusTest
JVM モードでは、@QuarkusTest
のアノテーションが付けられたテストは、テスト対象のアプリケーションと同じ JVM で実行されます。
これは、@Inject
を使用して、アプリケーションからテストコードに Bean を追加できることを意味します。
@jakarta.enterprise.inject.Alternative
および @jakarta.annotation.Priority
を使用して、新しい Bean を定義したり、アプリケーションから Bean をオーバーライドしたりすることもできます。
4.1.3.2. ネイティブモードでの @QuarkusIntegrationTest
ネイティブモードでは、@QuarkusIntegrationTest
のアノテーションが付けられたテストは、実行中のネイティブアプリケーションとは別のプロセスでホストされている JVM で実行されます。
この重要な結果として、テストとネイティブアプリケーション間のすべての通信は、次の 1 つ以上の形式を取る必要があります。
- ネットワークの呼び出し。通常は、HTTP またはアプリケーションがサポートするその他のネットワークプロトコルです。
-
ファイルシステムの変更を監視します。(例: Camel
file
エンドポイント経由) - その他の種類のプロセス間通信。
QuarkusIntegrationTest
は、@QuarkusTest
では利用できない追加機能を提供します。
- JVM モードでは、Quarkus ビルドによって生成された実行可能なアプリケーション JAR を起動してテストできます。
- ネイティブモードでは、Quarkus ビルドによって作成されたネイティブアプリケーションを起動してテストできます。
- コンテナーイメージをビルドに追加すると、コンテナーが起動し、それに対してテストが実行されます。
QuarkusIntegrationTest
の詳細は、Quarkus testing guide を参照してください。
4.1.4. 外部サービスによるテスト
4.1.4.1. Testcontainers
アプリケーションは、メッセージングブローカー、データベース、その他のサービスなどの外部リソースにアクセスする必要がある場合があります。
対象のサービスのコンテナーイメージが利用可能な場合は、Testcontainers を使用して、テスト中にサービスを起動および設定できます。
4.1.4.1.1. QuarkusTestResourceLifecycleManager
を使用して設定データを渡す
アプリケーションを正常に動作させるためには、多くの場合、接続設定データ (ホスト、ポート、ユーザー、リモートサービスのパスワード) をアプリケーションの起動前に渡すことが不可欠です。
Quarkus エコシステムでは、QuarkusTestResourceLifecycleManager
がこの目的を果たします。
start()
メソッドで 1 つ以上の Testcontainer を起動し、メソッドから接続設定を Map
の形式で返すことができます。
このマップのエントリーは、モードに応じてさまざまな方法でアプリケーションに渡されます。
-
ネイティブモード: コマンドライン (
-Dkey=value
) - JVM モード: 特別な MicroProfile 設定プロバイダー
コマンドラインおよび MicroProfile 設定は、application.properties
ファイルの設定よりも優先されます。
import java.util.Map; import java.util.HashMap; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; public class MyTestResource implements QuarkusTestResourceLifecycleManager { private GenericContainer<?> myContainer; @Override public Map<String, String> start() { // Start the needed container(s) myContainer = new GenericContainer(DockerImageName.parse("my/image:1.0.0")) .withExposedPorts(1234) .waitingFor(Wait.forListeningPort()); myContainer.start(); // Pass the configuration to the application under test // You can also pass camel component property names / values to automatically configure Camel components return new HashMap<>() {{ put("my-container.host", container.getHost()); put("my-container.port", "" + container.getMappedPort(1234)); }}; } @Override public void stop() { // Stop the needed container(s) myContainer.stop(); ... } }
import java.util.Map;
import java.util.HashMap;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
public class MyTestResource implements QuarkusTestResourceLifecycleManager {
private GenericContainer<?> myContainer;
@Override
public Map<String, String> start() {
// Start the needed container(s)
myContainer = new GenericContainer(DockerImageName.parse("my/image:1.0.0"))
.withExposedPorts(1234)
.waitingFor(Wait.forListeningPort());
myContainer.start();
// Pass the configuration to the application under test
// You can also pass camel component property names / values to automatically configure Camel components
return new HashMap<>() {{
put("my-container.host", container.getHost());
put("my-container.port", "" + container.getMappedPort(1234));
}};
}
@Override
public void stop() {
// Stop the needed container(s)
myContainer.stop();
...
}
}
@QuarkusTestResource
を使用して、テストクラスから定義済みのテストリソースを参照します。
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(MyTestResource.class) class MyTest { ... }
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@QuarkusTestResource(MyTestResource.class)
class MyTest {
...
}
サンプル実装は Camel Quarkus ソースにあります。
4.1.4.2. WireMock
ライブエンドポイントが利用できない、信頼性が低い、コストがかかる場合などには、テストをライブエンドポイントに接続する代わりに、サードパーティーのサービスおよび API との HTTP インタラクションをスタブできます。
WireMock を使用すると、HTTP インタラクションをモックおよび記録できます。これは、さまざまなコンポーネントエクステンション用に Camel Quarkus テストスイート全体で広く使用されています。
4.1.4.2.1. WireMock のセットアップ
手順
WireMock サーバーをセットアップします。
注記テストを行う Camel コンポーネントを常に設定して、すべての HTTP 対話を WireMock プロキシー経由で渡すようにします。これは、API エンドポイント URL を決定するコンポーネントプロパティーを設定することで実現できます。
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import java.util.HashMap; import java.util.Map; import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class WireMockTestResource implements QuarkusTestResourceLifecycleManager { private WireMockServer server; @Override public Map<String, String> start() { // Setup & start the server server = new WireMockServer( wireMockConfig().dynamicPort() ); server.start(); // Stub an HTTP endpoint. WireMock also supports a record and playback mode // https://wiremock.org/docs/record-playback/ server.stubFor( get(urlEqualTo("/api/greeting")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"message\": \"Hello World\"}"))); // Ensure the camel component API client passes requests through the WireMock proxy Map<String, String> conf = new HashMap<>(); conf.put("camel.component.foo.server-url", server.baseUrl()); return conf; } @Override public void stop() { if (server != null) { server.stop(); } } }
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import java.util.HashMap; import java.util.Map; import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class WireMockTestResource implements QuarkusTestResourceLifecycleManager { private WireMockServer server; @Override public Map<String, String> start() { // Setup & start the server server = new WireMockServer( wireMockConfig().dynamicPort() ); server.start(); // Stub an HTTP endpoint. WireMock also supports a record and playback mode // https://wiremock.org/docs/record-playback/ server.stubFor( get(urlEqualTo("/api/greeting")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"message\": \"Hello World\"}"))); // Ensure the camel component API client passes requests through the WireMock proxy Map<String, String> conf = new HashMap<>(); conf.put("camel.component.foo.server-url", server.baseUrl()); return conf; } @Override public void stop() { if (server != null) { server.stop(); } } }
Copy to Clipboard Copied! -
テストクラスに
@QuarkusTestResource
アノテーションがあり、適切なテストリソースクラスが値として指定されていることを確認します。WireMock サーバーは、すべてのテストが実行される前に起動され、すべてのテストが終了するとシャットダウンされます。
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(WireMockTestResource.class) class MyTest { ... }
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@QuarkusTestResource(WireMockTestResource.class)
class MyTest {
...
}
WireMock サーバーは、すべてのテストが実行される前に起動し、すべてのテストが終了するとシャットダウンします。
サンプルの実装は、Camel Quarkus 統合テストソースツリーにあります。
4.1.5. CamelQuarkusTestSupport
での CamelTestSupport
スタイルのテスト
Camel Quarkus 2.13.0 以降、CamelQuarkusTestSupport
をテストに使用できます。これは CamelTestSupport
の後継で、Quarkus では適切に機能しません。
CamelQuarkusTestSupport
は、JVM モードでのみ機能します。ネイティブモードでテストする必要がある場合は、上記の代替テストストラテジーのいずれかを使用します。
4.1.5.1. JVM モードでの CamelQuarkusTestSupport
によるテスト
次の依存関係をモジュール (できれば test
スコープ内) に追加します。
<dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-junit5</artifactId> <scope>test</scope> </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
次のように、テストで CamelQuarkusTestSupport
を使用できます。
@QuarkusTest @TestProfile(SimpleTest.class) //necessary only if "newly created" context is required for the test (worse performance) public class SimpleTest extends CamelQuarkusTestSupport { ... }
@QuarkusTest
@TestProfile(SimpleTest.class) //necessary only if "newly created" context is required for the test (worse performance)
public class SimpleTest extends CamelQuarkusTestSupport {
...
}
4.1.5.2. テスト用の CamelContext
のカスタマイズ
設定プロファイル、CDI Bean、オブザーバー、mocks などを使用してテスト用に CamelContext
をカスタマイズできます。createCamelContext
メソッドをオーバーライドして、CamelContext
と直接対話することもできます。
createCamelContext
を使用する場合は、新しい CamelContext
をインスタンス化して返さ ないようにしてください。代わりに、super.createCamelContext()
を呼び出して、返された CamelContext
を必要に応じて変更します。このルールに従わないと、例外が発生します。
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Override protected CamelContext createCamelContext() throws Exception { // Must call super to get a handle on the application scoped CamelContext CamelContext context = super.createCamelContext(); // Apply customizations context.setTracing(true); // Return the modified CamelContext return context; } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Override
protected CamelContext createCamelContext() throws Exception {
// Must call super to get a handle on the application scoped CamelContext
CamelContext context = super.createCamelContext();
// Apply customizations
context.setTracing(true);
// Return the modified CamelContext
return context;
}
}
4.1.5.3. テスト用のルートの設定
アプリケーション内の RouteBuilder
を拡張するすべてのクラスのルートは、CamelContext
に自動的に追加されます。同様に、camel.main.routes-include-pattern
から設定された XML または YAML ルートも読み込まれます。
これは必ずしもテストに適切であるとは限りません。設定プロパティーを使用して、テスト時にどのルートをロードするかを制御します。
-
quarkus.camel.routes-discovery.include-patterns
-
quarkus.camel.routes-discovery.exclude-patterns
-
camel.main.routes-include-pattern
-
camel.main.routes-exclude-pattern
createRouteBuilder
をオーバーライドして、テストクラスごとにテスト固有のルートを定義することもできます。
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Test void testGreeting() { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:start", "World"); mockEndpoint.assertIsSatisified(); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start") .transform().simple("Hello ${body}") .to("mock:result"); } }; } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Test
void testGreeting() {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:start", "World");
mockEndpoint.assertIsSatisified();
}
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start")
.transform().simple("Hello ${body}")
.to("mock:result");
}
};
}
}
4.1.5.4. CamelContext テストライフサイクル
CamelTestSupport
と比較した CamelQuarkusTestSupport
の主な違いの 1 つは、CamelContext
ライフサイクルがどのように管理されるかです。
Camel Quarkus では、ランタイムによって単一の CamelContext
が自動的に作成されます。デフォルトでは、この CamelContext
はすべてのテスト間で共有され、これは、テストスイート全体が実行中は起動状態のままになります。
これにより、テストに予期しない副作用が生じる可能性があります。テスト間で CamelContext
を再起動する必要がある場合は、カスタム テストプロファイル を作成して、テスト対象のアプリケーションを強制的に再起動できます。
たとえば、テストプロファイルを定義するには、次のようにします。
@QuarkusTest class MyTestProfile implements QuarkusTestProfile { ... }
@QuarkusTest
class MyTestProfile implements QuarkusTestProfile {
...
}
次に、@TestProfile
を使用してテストクラスで参照します。
// @TestProfile will trigger the application to be restarted @TestProfile(MyTestProfile.class) @QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { ... }
// @TestProfile will trigger the application to be restarted
@TestProfile(MyTestProfile.class)
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
...
}
CamelContext
の stop()
メソッドと start()
メソッドを呼び出して手動で再起動できません。これにより、例外が発生します。
4.1.5.5. 例
4.1.5.5.1. シンプルな RouteBuilder
およびテストクラス
シンプルな RouteBuilder
:
public class MyRoutes extends RouteBuilder { @Override public void configure() { from("direct:start") .transform().simple("Hello ${body}") .to("mock:result"); } }
public class MyRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.transform().simple("Hello ${body}")
.to("mock:result");
}
}
メッセージペイロードを direct:start
エンドポイントに送信するテストを実行します。
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Test void testGreeting() { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:start", "World"); mockEndpoint.assertIsSatisified(); } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Test
void testGreeting() {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:start", "World");
mockEndpoint.assertIsSatisified();
}
}
4.1.5.5.2. AdviceWith
の使用
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BeforeEach public void beforeEach() throws Exception { AdviceWith.adviceWith(this.context, "advisedRoute", route -> { route.replaceFromWith("direct:replaced"); }); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start").routeId("advisedRoute") .transform().simple("Hello ${body}") .to("mock:result"); } }; } @Test void testAdvisedRoute() throws Exception { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:replaced", "World"); mockEndpoint.assertIsSatisfied(); } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@BeforeEach
public void beforeEach() throws Exception {
AdviceWith.adviceWith(this.context, "advisedRoute", route -> {
route.replaceFromWith("direct:replaced");
});
}
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").routeId("advisedRoute")
.transform().simple("Hello ${body}")
.to("mock:result");
}
};
}
@Test
void testAdvisedRoute() throws Exception {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:replaced", "World");
mockEndpoint.assertIsSatisfied();
}
}
4.1.5.5.3. アドバイスの明示的な有効化
アドバイスを明示的に有効にする 場合は、AdviceWith
のセットアップを完了するときに startRouteDefinitions
を呼び出す必要があります。
startRouteDefinitions
を呼び出す必要があるのは、アドバイスされて いない ルートが設定されている場合のみです。
4.1.5.6. 制限
4.1.5.6.1. CamelTestSupport
から継承されたテストライフサイクルメソッド
CamelQuarkusTestSupport
は、CamelTestSupport
から一部のテストライフサイクルメソッドを継承します。ただし、これらは使用すべきではなく、代わりに CamelQuarkusTestSupport
の同等のメソッドに置き換えられます。
CamelTestSupport ライフサイクルメソッド | CamelQuarkusTestSupport 相当 |
---|---|
|
|
|
|
|
|
|
|
4.1.5.6.2. カスタム Camel レジストリーの作成はサポートされていません
createCamelRegistry
の CamelQuarkusTestSupport
実装は UnsupportedOperationException
を出力します。
オブジェクトを Camel レジストリーにバインドまたはバインド解除する必要がある場合は、次のいずれかの方法で実行できます。
名前付き CDI Bean を生成する
public class MyBeanProducers { @Produces @Named("myBean") public MyBean createMyBean() { return new MyBean(); } }
public class MyBeanProducers { @Produces @Named("myBean") public MyBean createMyBean() { return new MyBean(); } }
Copy to Clipboard Copied! -
createCamelContext
をオーバーライドし (上記の例を参照)、camelContext.getRegistry().bind ("foo"、fooBean)
を呼び出す。 @BindToRegistry
アノテーションを使用する。@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BindToRegistry("myBean") MyBean myBean = new MyBean(); }
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BindToRegistry("myBean") MyBean myBean = new MyBean(); }
Copy to Clipboard Copied! 注記個々のテストクラスから Camel レジストリーにバインドされた Bean は、テストスイートの実行中は保持されます。テストの期待値によっては、予期しない結果が生じる可能性があります。これを回避するには、テストプロファイルを使用して
CamelContext
を再起動できます。