Camel Quarkus

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, 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.

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:

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 Quarkus testing guide. Refer to Camel Quarkus source tree for a 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 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 Twilio.

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.

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 validator tests.

Citrus Testing Framework

The Citrus Testing Framework provides advanced integration testing capabilities for Camel Quarkus applications. For detailed information about using Citrus with Quarkus test support, see Integration Testing with Citrus in the Apache Camel documentation.