= Testing
:page-aliases: testing.adoc

Testing offers a good way to ensure camel routes behave as expected over time.
Before going deeper into the subject, it is strongly advised to read xref:user-guide/first-steps.adoc[First Steps] and https://quarkus.io/guides/getting-started-testing[Quarkus testing].

When it comes to testing a route in the context of Quarkus, the recommended approach is to write local integration tests.
This has the advantage of covering both JVM and native mode.

xref:#_cameltestsupport_style_of_testing[`CamelTestSupport`] style testing can also be used. Note that it can only work in JVM mode.

== A test running in JVM mode

All tests should be annotated with `@QuarkusTest`. This will bootstrap Quarkus and start Camel routes before the `@Test` logic is executed,
like in the example below:

[source,java]
----

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

@QuarkusTest
class MyTest {
    @Test
    void test() {
        // Use any suitable code that send test data to the route and then assert outcomes
    }
}
----

An example implementation can be found https://github.com/apache/camel-quarkus/blob/main/integration-tests/bindy/src/test/java/org/apache/camel/quarkus/component/bindy/it/MessageTest.java[here].

[[native-tests]]
== A test running in native mode

Providing all extensions your application depends on are supported in native mode,
you should test that your application works correctly native mode.
The test logic defined in JVM mode can be reused in native mode, by inheriting from the respective JVM mode class.
The `@QuarkusIntegrationTest` annotation instructs the Quarkus JUnit extension to compile the application under test to a native image
and will start it before running the tests.

[source,java]
----
import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
class MyIT extends MyTest {
}
----

An example implementation of a native can be found https://github.com/apache/camel-quarkus/blob/main/integration-tests/bindy/src/test/java/org/apache/camel/quarkus/component/bindy/it/MessageRecordIT.java[here].

[[jvm-vs-native-tests]]
== `@QuarkusTest` Vs `@QuarkusIntegrationTest`

JVM mode tests annotated with `@QuarkusTest` are executed in the same JVM as the application under test.
This makes it possible to https://quarkus.io/guides/getting-started-testing#injection-into-tests[`@Inject`] CDI beans from the application into the test code.
You can also define new beans or even override beans from the application using https://quarkus.io/guides/getting-started-testing#cdi-alternative-mechanism[`@jakarta.enterprise.inject.Alternative`] and https://quarkus.io/guides/getting-started-testing#cdi-alternative-mechanism[`@jakarta.annotation.Priority`].

These options do not work in native mode for tests annotated with `@QuarkusIntegrationTest`, as they are executed in a JVM hosted in a process separate from the running native application.

An important consequence of this, is that all communication between the tests and the native application,
must take one or more of the following forms:

* Network calls. Typically, HTTP or any other network protocol your application supports.
* Watching the filesystem for changes (E.g. via Camel `file` endpoints).
* Any other kind of interprocess communication.

`QuarkusIntegrationTest` also provides some additional features that are not available through `@QuarkusTest`.

* In JVM mode it can launch and test the runnable application JAR produced by the Quarkus build.
* In native mode it can launch and test the native application produced by the Quarkus build.
* If a container image was created during the build, then a container is started so that tests can be executed against it.

For more information about `QuarkusIntegrationTest` see the https://quarkus.io/guides/getting-started-testing#quarkus-integration-test[Quarkus testing guide].

== Testing with external services

=== Testcontainers

Sometimes your application needs to access an external service such as a message broker, database, etc.
If a container image is available for the service of interest, https://www.testcontainers.org/[Testcontainers]
can be used to start and stop these services for testing.

For the application to work properly, it is often essential to pass the connection configuration data
(host, port, user, password, etc. of the remote service) to the application before it starts.
In the Quarkus ecosystem, `QuarkusTestResourceLifecycleManager` serves this purpose.

You can start one or more containers in the `start()` method
and return the connection configuration in the form of a `Map`, where the key is the name of a configuration property and the value is the property value.
The entries of this map are then passed to the application either via command line (`-Dkey=value`) in native mode,
or through a special MicroProfile configuration provider in JVM mode.
Note that these settings have a higher precedence than the settings in `application.properties` file.

[source,java]
----
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();
    }
}
----

The defined test resource needs to be referenced from the test classes with `@QuarkusTestResource` as shown below:

[source,java]
----
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(MyTestResource.class)
class MyTest {
   ...
}
----

More information can be found in the https://quarkus.io/guides/getting-started-testing#quarkus-test-resource[Quarkus testing guide]. Refer to Camel Quarkus source tree for a https://github.com/apache/camel-quarkus/blob/main/integration-tests/google-storage/src/test/java/org/apache/camel/quarkus/component/google/storage/it/GoogleStorageTestResource.java[complete example].

=== WireMock

It is sometimes useful to stub HTTP interactions with third party services & APIs so that tests do not have to connect to live endpoints, as this can incur costs and the service may not always be 100% available or reliable.

An excellent tool for mocking & recording HTTP interactions is http://wiremock.org/[WireMock]. It is used extensively throughout the Camel Quarkus test suite for various component extensions. Here follows a typical workflow
for setting up WireMock.

First set up the WireMock server. Note that it is important to configure the Camel component under test to pass any HTTP interactions through the WireMock proxy. This is usually achieved by configuring a component property
that determines the API endpoint URL. Sometimes things are less straightforward and some extra work is required to configure the API client library, as was the case for https://github.com/apache/camel-quarkus/blob/main/integration-tests/twilio/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java#L83[Twilio].

[source,java]
----
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. Note that 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();
        }
    }
}
----

Finally, ensure your test class has the `@QuarkusTestResource` annotation with the appropriate test resource class specified as the value. The WireMock server will be started before all tests are
executed and will be shut down when all tests are finished.

[source,java]
----
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(WireMockTestResource.class)
class MyTest {
   ...
}
----

More examples of WireMock usage can be found in the Camel Quarkus integration test source tree such as in the https://github.com/apache/camel-quarkus/blob/main/integration-tests/validator/src/test/java/org/apache/camel/quarkus/component/validator/it/ValidatorTestResource.java[validator tests].

=== Citrus Testing Framework

The https://citrusframework.org/[Citrus Testing Framework] provides advanced integration testing capabilities for Camel Quarkus applications.
For detailed information about using Citrus with Quarkus test support, see xref:manual::integration-test.adoc#_quarkus_test_support[Integration Testing with Citrus] in the Apache Camel documentation.

== `CamelTestSupport` style of testing

If you used Camel standalone or on other runtimes before, you may know `CamelTestSupport` already.
The original `CamelTestSupport` class is not well suited to Quarkus, so there's an extended Quarkus friendly version called `CamelQuarkusTestSupport`.

> IMPORTANT: `CamelQuarkusTestSupport` only works in JVM mode. If you need to test in native mode, then use one of the alternate test strategies described above.

To use `CamelQuarkusTestSupport`, you must add `camel-quarkus-junit` as a test scoped dependency to your application.

[source,xml]
----
<dependency>
    <groupId>org.apache.camel.quarkus</groupId>
    <artifactId>camel-quarkus-junit</artifactId>
    <scope>test</scope>
</dependency>
----

=== Customizing the `CamelContext` for testing

You can customize the `CamelContext` for testing with https://quarkus.io/guides/config-reference#profiles[configuration profiles], CDI beans, observers, https://quarkus.io/guides/getting-started-testing#mock-support[mocks] etc.
You can also override the `createCamelContext` method and interact directly with the `CamelContext`.

IMPORTANT: When using `createCamelContext` you *MUST NOT*  instantiate and return a new `CamelContext`. Instead, invoke `super.createCamelContext()` and modify the returned `CamelContext` as needed.
Failing to follow this rule will result in an exception being thrown.

[source,java]
----
@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;
    }
}
----

=== Configuring routes for testing

Any classes that extend `RouteBuilder` in your application will have their routes automatically added to the `CamelContext`.
Similarly, any XML or YAML routes configured from `camel.main.routes-include-pattern` will also be loaded.

This may not always be desirable for your tests.
You control which routes get loaded at test time with configuration properties:

* `quarkus.camel.routes-discovery.include-patterns`
* `quarkus.camel.routes-discovery.exclude-patterns`,
* `camel.main.routes-include-pattern`
* `camel.main.routes-exclude-pattern`.

You can also define test specific routes per test class by overriding `createRouteBuilder`:

[source,java]
----
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
    @Test
    void testGreeting() {
        MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
        mockEndpoint.expectedBodiesReceived("Hello World");

        template.sendBody("direct:start", "World");

        mockEndpoint.assertIsSatisfied();
    }

    @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");
            }
        };
    }
}
----

=== CamelContext test lifecycle

One of the main differences in `CamelQuarkusTestSupport` compared to `CamelTestSupport` is how the `CamelContext` lifecycle is managed.

On Camel Quarkus, a single `CamelContext` is created for you automatically by the runtime.
By default, this `CamelContext` is shared among all tests and remains started for the duration of the entire test suite execution.

This can potentially have some unintended side effects for your tests.
If you need to have the `CamelContext` restarted between tests, then you can create a custom https://quarkus.io/guides/getting-started-testing#testing_different_profiles[test profile], which will force the application under test to be restarted.

For example, to define a test profile:

[source,java]
----
@QuarkusTest
class MyTestProfile implements QuarkusTestProfile {
    ...
}
----

Then reference it on the test class with `@TestProfile`:

[source,java]
----
// @TestProfile will trigger the application to be restarted
@TestProfile(MyTestProfile.class)
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
    ...
}
----

NOTE: You cannot manually restart the `CamelContext` by invoking its `stop()` and `start()` methods. This will result in an exception.

=== Examples

==== Simple `RouteBuilder` and test class

Simple `RouteBuilder`:

[source,java]
----
public class MyRoutes extends RouteBuilder {
    @Override
    public void configure() {
        from("direct:start")
            .transform().simple("Hello ${body}")
            .to("mock:result");
    }
}
----

Test sending a message payload to the `direct:start` endpoint:

[source,java]
----
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
    @Test
    void testGreeting() {
        MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
        mockEndpoint.expectedBodiesReceived("Hello World");

        template.sendBody("direct:start", "World");

        mockEndpoint.assertIsSatisfied();
    }
}
----

==== Using `AdviceWith`

xref:manual::advice-with.adoc[AdviceWith] allows you to modify routes at runtime for testing purposes - replacing endpoints, adding interceptors, or changing route behavior without modifying production code.

===== Advising existing application routes

The recommended pattern is:

1. Define routes in your *application code* (src/main/java)
2. Use `AdviceWith` to modify these *existing* routes in your tests

[source,java]
----
// Application code: src/main/java/com/example/MyRoutes.java
public class MyRoutes extends RouteBuilder {
    @Override
    public void configure() {
        from("kafka:input-topic").routeId("my-kafka-route")
            .to("kafka:output-topic")
            .to("mock:result");
    }
}

// Test code: src/test/java/com/example/MyRoutesTest.java
@QuarkusTest
class MyRoutesTest extends CamelQuarkusTestSupport {
    @BeforeEach
    public void beforeEach() throws Exception {
        // Advise the application route
        AdviceWith.adviceWith(this.context, "my-kafka-route", route -> {
            route.weaveByToUri("kafka:output-topic*").replace().to("mock:kafka-stub");
        });
    }

    @Test
    void testRoute() throws Exception {
        MockEndpoint stub = getMockEndpoint("mock:kafka-stub");
        stub.expectedMessageCount(1);

        template.sendBody("kafka:input-topic", "test");

        stub.assertIsSatisfied();
    }
}
----

WARNING: Advising routes created via `createRouteBuilder()` in tests can lead to unpredictable behavior.

===== Apply advice in `@BeforeEach` or `doBeforeEach`

When all tests in a class need the same advice, apply it in `@BeforeEach` or override `doBeforeEach`:

[source,java]
----
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
    @BeforeEach
    public void beforeEach() throws Exception {
        // Apply advice to EXISTING application route
        AdviceWith.adviceWith(this.context, "advisedRoute", route -> {
            route.replaceFromWith("direct:replaced");
        });
        // Routes that aren't advised are automatically started after this method completes
    }

    @Test
    void testAdvisedRoute() throws Exception {
        MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
        mockEndpoint.expectedBodiesReceived("Hello World");

        template.sendBody("direct:replaced", "World");

        mockEndpoint.assertIsSatisfied();
    }
}
----

Routes that aren't advised are automatically started after `@BeforeEach` or `doBeforeEach` completes. Advice modifications are automatically cleaned up between test methods

===== Per-test-method advice using `adviceRoute()`

When you need *different* advice in each test method, use the `adviceRoute()` helper method which handles route lifecycle (stop, advice, start) automatically:

[source,java]
----
@QuarkusTest
class PerMethodAdviceTest extends CamelQuarkusTestSupport {
    @Test
    void testWithMockOutput() throws Exception {
        // Apply advice specific to this test on EXISTING application route
        adviceRoute("my-route", route -> {
            route.weaveByToUri("kafka:output*").replace().to("mock:kafka-stub");
        });

        MockEndpoint stub = getMockEndpoint("mock:kafka-stub");
        stub.expectedMessageCount(1);

        template.sendBody("kafka:input", "test message");

        stub.assertIsSatisfied();
    }

    @Test
    void testWithDifferentAdvice() throws Exception {
        // Different advice for this test on the same EXISTING route
        adviceRoute("my-route", route -> {
            route.weaveByToUri("kafka:output*").replace()
                .transform(simple("MODIFIED: ${body}"))
                .to("mock:result");
        });

        // ... different test assertions
    }
}
----

===== Advice patterns

IMPORTANT: When using `weaveByToUri()`, `weaveByType()`, or similar methods, use specific patterns rather than wildcards to avoid unintentionally matching endpoints from the main application or other routes.

For example, instead of:
[source,java]
----
// Matches ALL Kafka endpoints in the context
route.weaveByToUri("kafka:*").replace().to("mock:result");
----

Use specific endpoint URIs:
[source,java]
----
// Only matches the specific endpoint you intend to advise
route.weaveByToUri("kafka:my-output-topic*").replace().to("mock:result");
----

Or use IDs for even more precision:
[source,java]
----
// Uses ID for exact matching
route.weaveById("kafkaProducer").replace().to("mock:result");
----

Wildcard patterns like `kafka:*`, `http:*`, or `direct:*` will match **all** endpoints of that type throughout the entire `CamelContext`, including routes from your main application that the test may not need to modify. This can cause unexpected test failures and interference between tests.

===== Advice cleanup between tests

Advice modifications are automatically cleaned up between test methods to prevent interference. This ensures that:

* Each test starts with a clean route state
* Advice from one test doesn't affect subsequent tests
* Tests remain independent and can run in any order

=== Limitations

==== Test lifecycle methods inherited from `CamelTestSupport`

`CamelQuarkusTestSupport` inherits some test lifecycle methods from `CamelTestSupport`. However, they should not be used and instead are replaced with equivalent methods in `CamelQuarkusTestSupport`.

|===
|CamelTestSupport lifecycle methods |CamelQuarkusTestSupport equivalent

|`afterAll`
|`doAfterAll`

|`afterEach`, `afterTestExecution`
|`doAfterEach`

|`beforeAll`
|`doAfterConstruct`

|`beforeEach`
|`doBeforeEach`
|===

==== Creating a custom Camel registry is not supported

The `CamelQuarkusTestSupport` implementation of `createCamelRegistry` will throw `UnsupportedOperationException`.

If you need to bind or unbind objects to the Camel registry, then you can do it by one of the following methods.

* Produce named CDI beans
+
[source,java]
----
public class MyBeanProducers {
    @Produces
    @Named("myBean")
    public MyBean createMyBean() {
        return new MyBean();
    }
}
----
+
* Override `createCamelContext` (see example above) and invoke `camelContext.getRegistry().bind("foo", fooBean)`
* Use the `@BindToRegistry` annotation
+
[source,java]
----
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
    @BindToRegistry("myBean")
    MyBean myBean = new MyBean();
}
----
+
+

NOTE: Beans bound to the Camel registry from individual test classes, will persist for the duration of the test suite execution.
This could have unintended consequences, depending on your test expectations. You can use test profiles to restart the `CamelContext` to avoid this.
