Skip to content

Latest commit

 

History

History
2570 lines (2140 loc) · 77.9 KB

File metadata and controls

2570 lines (2140 loc) · 77.9 KB

Apache Camel support

Apache Camel is a fantastic Open Source integration framework that empowers you to quickly and easily integrate various systems consuming or producing data.

Camel implements the enterprise integration patterns for building mediation and routing rules in your software. With the Camel support in Citrus you are able to directly interact with the 300+ Camel components through route definitions. You can call Camel routes and receive synchronous response messages from a Citrus test. You can also simulate the Camel route endpoint with receiving messages and providing simulated response messages.

Note
The camel components in Citrus are located in a separate Maven module. So you should add the module as Maven dependency to your project accordingly.
<dependency>
  <groupId>org.citrusframework</groupId>
  <artifactId>citrus-camel</artifactId>
  <version>${citrus.version}</version>
</dependency>

For XML configurations Citrus provides a special Camel configuration schema that is used in Spring configuration files. You have to include the citrus-camel namespace in your Spring configuration XML files as follows.

XML
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:citrus="http://www.citrusframework.org/schema/config"
      xmlns:citrus-camel="http://www.citrusframework.org/schema/camel/config"
      xsi:schemaLocation="
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.citrusframework.org/schema/config
          http://www.citrusframework.org/schema/config/citrus-config.xsd
          http://www.citrusframework.org/schema/camel/config
          http://www.citrusframework.org/schema/camel/config/citrus-camel-config.xsd">

      [...]

      </beans>

Now you are ready to use the Camel configuration elements using the citrus-camel namespace prefix.

The next sections explain the Citrus capabilities while working with Apache Camel.

Camel endpoint

Camel and Citrus both use the endpoint pattern in order to define message destinations. Users can interact with these endpoints when creating the mediation and routing logic. The Citrus endpoint component for Camel interaction is defined as follows in your Citrus Spring configuration.

Java
@Bean
public CamelEndpoint helloCamelEndpoint() {
    return new CamelEndpointBuilder()
        .endpointUri("direct:hello")
        .build();
}
XML
<citrus-camel:endpoint id="helloCamelEndpoint"
      endpoint-uri="direct:hello"/>

The endpoint defines an endpoint uri that will be called when messages are sent/received using the endpoint producer or consumer. The endpoint uri refers to a Camel route that is located inside a Camel context. The Camel route should use the respective endpoint uri as from definition.

Java
@Bean
public CamelContext camelContext() {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:hello")
                .routeId("helloRoute")
                .to("log:org.citrusframework.camel?level=INFO")
                .to("seda:greetings");
        }
    });

    return context;
}
XML
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="helloRoute">
    <from uri="direct:hello"/>
    <to uri="log:org.citrusframework.camel?level=INFO"/>
    <to uri="seda:greetings-feed"/>
  </route>
</camelContext>

In the example above the Camel context is placed as Spring bean. The context defines a Camel route the Citrus test case can interact with.

The Camel context is automatically referenced in the Citrus Camel endpoint. This is because Citrus will automatically look for a Camel context in the Spring bean configuration.

In case you have multiple Camel context instances in your configuration you can explicitly link the endpoint to a context with camel-context="camelContext".

Java
@Bean
public CamelEndpoint helloCamelEndpoint() {
    return new CamelEndpointBuilder()
        .camelContext(specialCamelContext)
        .endpointUri("direct:hello")
        .build();
}
XML
<citrus-camel:endpoint id="helloCamelEndpoint"
      camel-contxt="specialCamelContext"
      endpoint-uri="direct:hello"/>

This explicitly binds the endpoint to the context named "specialCamelContext". This configuration would be the easiest setup to use Camel with Citrus as you can add the Camel context straight to the Spring bean application context and interact with it in Citrus. Of course, you can also import your Camel context and routes from other Spring bean context files, or you can start the Camel context routes with Java code.

In the example the Camel route is listening on the route endpoint uri direct:hello. Incoming messages will be logged to the console using a log Camel component. After that the message is forwarded to a seda Camel component which is a simple queue in memory.

The Citrus endpoint can interact with this sample route definition sending messages to the direct:hello endpoint. The endpoint configuration holds the endpoint uri information that tells Citrus how to access the Camel route. This endpoint uri can be any Camel endpoint uri that is used in a Camel route.

The Camel routes support asynchronous and synchronous message communication patterns. By default, Citrus uses asynchronous communication with Camel routes. This means that the Citrus producer sends the exchange message to the route endpoint uri and is finished immediately. There is no synchronous response to await. In contrary to that the synchronous endpoint will send and receive a synchronous message on the Camel destination route. This message exchange pattern is discussed in a later section in this chapter.

For now, we have a look on how to use the Citrus Camel endpoint in a test case in order to send a message to the Camel route:

Java
send(helloCamelEndpoint)
    .message()
    .body("Hello from Citrus!");
XML
<send endpoint="helloCamelEndpoint">
  <message type="plaintext">
    <payload>Hello from Citrus!</payload>
  </message>
</send>

You can use the very same Citrus Camel endpoint component to receive messages in your test case, too. In this situation you would receive a message from the route endpoint. This is especially designed for queueing endpoint routes such as the Camel seda component. In our example Camel route above the seda Camel component is called with the endpoint uri seda:greetings-feed.

This means that the Camel route is sending a message to the seda component. Citrus is able to receive this route message with an endpoint component like this:

Java
@Bean
public CamelEndpoint greetingsFeed() {
    return new CamelEndpointBuilder()
        .endpointUri("seda:greetings-feed")
        .build();
}
XML
<citrus-camel:endpoint id="greetingsFeed"
    endpoint-uri="seda:greetings-feed"/>

You can use the Citrus camel endpoint in your test case receive action in order to consume the message on the seda component.

Java
receive(greetingsFeed)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Citrus!");
XML
<receive endpoint="greetingsFeed">
  <message type="plaintext">
    <payload>Hello from Citrus!</payload>
  </message>
</receive>
Tip
Instead of defining a static Citrus camel component you could also use the dynamic endpoint components in Citrus. This would enable you to send your message directly using the endpoint uri direct:news in your test case. Read more about this in dynamic-endpoint-components.

Citrus is able to send and receive messages with Camel route endpoint uri. This enables you to invoke a Camel route. The Camel components used is defined by the endpoint uri as usual. When interacting with Camel routes you might need to send back some response messages in order to simulate boundary applications. We will discuss the synchronous communication in the next section.

Synchronous Camel endpoint

The synchronous Camel producer sends a message to a route and waits synchronously for the response to arrive. In Camel this communication is represented with the exchange pattern InOut. The basic configuration for a synchronous Camel endpoint component looks like follows:

Java
@Bean
public CamelSyncEndpoint helloCamelEndpoint() {
    return new CamelSyncEndpointBuilder()
        .endpointUri("direct:hello")
        .timeout(1000L)
        .pollingInterval(300L)
        .build();
}
XML
<citrus-camel:sync-endpoint id="helloCamelEndpoint"
      endpoint-uri="direct:hello"
      timeout="1000"
      polling-interval="300"/>

Synchronous endpoints poll for synchronous reply messages to arrive. The poll interval is an optional setting in order to manage the amount of reply message handshake attempts. Once the endpoint was able to receive the reply message synchronously the test case can receive the reply. In case the reply message is not available in time we raise some timeout error and the test will fail.

In a first test scenario we write a test case that sends a message to the synchronous endpoint and waits for the synchronous reply message to arrive. So we have two actions on the same Citrus endpoint, first send then receive.

Java
send(helloCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Citrus!");

receive(helloCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("This is the reply from Apache Camel!");
XML
<send endpoint="helloCamelEndpoint">
  <message type="plaintext">
    <payload>Hello from Citrus!</payload>
  </message>
</send>

<receive endpoint="helloCamelEndpoint">
  <message type="plaintext">
    <payload>This is the reply from Apache Camel!</payload>
  </message>
</receive>

The next variation deals with the same synchronous communication, but send and receive roles are switched. Now Citrus receives a message from a Camel route and has to provide a reply message. We handle this synchronous communication with the same synchronous Apache Camel endpoint component. Only difference is that we initially start the communication by receiving a message from the endpoint. Knowing this Citrus is able to send a synchronous response back. Again just use the same endpoint reference in your test case. So we have again two actions in our test case, but this time first receive then send.

Java
receive(helloCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Apache Camel!");

send(helloCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("This is the reply from Citrus!");
XML
<receive endpoint="helloCamelEndpoint">
  <message type="plaintext">
    <payload>Hello from Apache Camel!</payload>
  </message>
</receive>

<send endpoint="helloCamelEndpoint">
  <message type="plaintext">
    <payload>This is the reply from Citrus!</payload>
  </message>
</send>

This is pretty simple. Citrus takes care of setting the Camel exchange pattern InOut while using synchronous communications. The Camel routes do respond and Citrus is able to receive the synchronous messages accordingly. With this pattern you can interact with Camel routes where Citrus simulates synchronous clients and consumers.

Camel exchange headers

Camel uses exchanges when sending and receiving messages to and from routes. These exchanges hold specific information on the communication outcome. Citrus automatically converts these exchange information to special message header entries. You can validate those exchange headers then easily in your test case:

Java
receive(greetingsFeed)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Camel!")
    .header("citrus_camel_route_id", "greetings")
    .header("citrus_camel_exchange_id", "ID-local-50532-1402653725341-0-3")
    .header("citrus_camel_exchange_failed", false)
    .header("citrus_camel_exchange_pattern", "InOnly")
    .header("CamelCorrelationId", "ID-local-50532-1402653725341-0-1")
    .header("CamelToEndpoint", "seda://greetings-feed");
XML
<receive endpoint="greetingsFeed">
  <message type="plaintext">
    <payload>Hello from Camel!</payload>
  </message>
  <header>
    <element name="citrus_camel_route_id" value="greetings"/>
    <element name="citrus_camel_exchange_id" value="ID-local-50532-1402653725341-0-3"/>
    <element name="citrus_camel_exchange_failed" value="false"/>
    <element name="citrus_camel_exchange_pattern" value="InOnly"/>
    <element name="CamelCorrelationId" value="ID-local-50532-1402653725341-0-1"/>
    <element name="CamelToEndpoint" value="seda://greetings-feed"/>
  </header>
</receive>

In addition to the Camel specific exchange information the Camel exchange does also hold some custom properties. These properties such as CamelToEndpoint or CamelCorrelationId are also added automatically to the Citrus message header so can expect them in a receive message action.

Camel exception handling

Let us suppose the following route definition:

Java
@Bean
public CamelContext camelContext() {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:hello")
                .routeId("helloRoute")
                .to("log:org.citrusframework.camel?level=INFO")
                .to("seda:greetings-feed")
                .onException(CitrusRuntimeException.class)
                    .to("seda:exceptions");
        }
    });

    return context;
}
XML
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="helloRoute">
    <from uri="direct:hello"/>
    <to uri="log:org.citrusframework.camel?level=INFO"/>
    <to uri="seda:greetings-feed"/>
    <onException>
      <exception>org.citrusframework.exceptions.CitrusRuntimeException</exception>
      <to uri="seda:exceptions"/>
    </onException>
  </route>
</camelContext>

The route has an exception handling block defined that is called as soon as the exchange processing ends up in some error or exception. With Citrus you can also simulate an exchange exception when sending back a synchronous response to a calling route.

Java
send(helloCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Something went wrong!")
    .header("citrus_camel_exchange_exception", CitrusRuntimeException.class)
    .header("citrus_camel_exchange_exception_message", "Something went wrong!")
    .header("citrus_camel_exchange_failed", true);
XML
<send endpoint="greetingsFeed">
  <message type="plaintext">
    <payload>Something went wrong!</payload>
  </message>
  <header>
    <element name="citrus_camel_exchange_exception"
                value="org.citrusframework.exceptions.CitrusRuntimeException"/>
    <element name="citrus_camel_exchange_exception_message" value="Something went wrong!"/>
    <element name="citrus_camel_exchange_failed" value="true"/>
  </header>
</send>

This message as response to the seda:greetings-feed route would cause Camel to enter the exception handling in the route definition. The exception handling is activated and calls the error handling route endpoint seda:exceptions . Of course Citrus would be able to receive such an exception exchange validating the exception handling outcome.

In such failure scenarios the Camel exchange holds the exception information (CamelExceptionCaught) such as causing exception class and error message. These headers are present in an error scenario and can be validated in Citrus when receiving error messages as follows:

Java
receive(errorCamelEndpoint)
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Something went wrong!")
    .header("citrus_camel_route_id", "helloRoute")
    .header("citrus_camel_exchange_failed", true)
    .header("CamelExceptionCaught", "org.citrusframework.exceptions.CitrusRuntimeException: Something went wrong!");
XML
<receive endpoint="errorCamelEndpoint">
  <message type="plaintext">
    <payload>Something went wrong!</payload>
  </message>
  <header>
    <element name="citrus_camel_route_id" value="helloRoute"/>
    <element name="citrus_camel_exchange_failed" value="true"/>
    <element name="CamelExceptionCaught"
        value="org.citrusframework.exceptions.CitrusRuntimeException: Something went wrong!"/>
  </header>
</receive>

This completes the basic exception handling in Citrus when using the Camel endpoints.

Camel context handling

In the previous samples we have used the Camel context as Spring bean context that is automatically loaded when Citrus starts up. Now when using a single Camel context instance Citrus is able to automatically pick this Camel context for route interaction. If you use more than one Camel context you have to tell the Citrus endpoint component which context to use. The endpoint offers an optional attribute called camel-context.

Java
@Bean
public CamelEndpoint newsCamelEndpoint() {
    return new CamelEndpointBuilder()
        .camelContext(newsContext())
        .endpointUri("direct:news")
        .build();
}

@Bean
public CamelContext newsContext() {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:news")
                .routeId("newsRoute")
                .to("log:org.citrusframework.camel?level=INFO")
                .to("seda:news-feed");
        }
    });

    return context;
}

@Bean
public CamelContext helloContext() {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:hello")
                .routeId("helloRoute")
                .to("log:org.citrusframework.camel?level=INFO")
                .to("seda:greetings");
        }
    });

    return context;
}
XML
<citrus-camel:endpoint id="newsCamelEndpoint"
    camel-context="newsContext"
    endpoint-uri="direct:news"/>

<camelContext id="newsContext" xmlns="http://camel.apache.org/schema/spring">
    <route id="newsRoute">
      <from uri="direct:news"/>
      <to uri="log:org.citrusframework.camel?level=INFO"/>
      <to uri="seda:news-feed"/>
    </route>
</camelContext>

<camelContext id="helloContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="helloRoute">
    <from uri="direct:hello"/>
    <to uri="log:org.citrusframework.camel?level=INFO"/>
    <to uri="seda:greetings"/>
  </route>
</camelContext>

In the example above we have two Camel context instances loaded. The endpoint has to pick the context to use with the attribute camel-context which resides to the Spring bean id of the Camel context.

Camel route actions

Since Citrus 2.4 we introduced some Camel specific test actions that enable easy interaction with Camel routes and the Camel context.

Note
In XML the Camel route test actions do follow a specific XML namespace. This means you have to add this namespace to the test case when using the actions.
XML
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:camel="http://www.citrusframework.org/schema/camel/testcase"
      xsi:schemaLocation="
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.citrusframework.org/schema/camel/testcase
          http://www.citrusframework.org/schema/camel/testcase/citrus-camel-testcase.xsd">

  [...]

</beans>

Once you have added the special Camel namespace with prefix camel you are ready to start using the Camel test actions in your test case.

Create Camel routes

You can create a new Camel route as part of the test using this test action.

Java
public class CamelRouteActionIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void createCamelRoute() {
        $(camel().camelContext(camelContext)
            .route()
            .create(new RouteBuilder() {
                @Override
                public void configure() throws Exception {
                    from("direct:messages")
                        .routeId("message-tokenizer")
                        .split().tokenize(" ")
                        .to("seda:words");
                }
            }));
    }
}
XML
<testcase name="CamelRouteIT">
  <actions>
      <camel:create-routes>
        <routeContext xmlns="http://camel.apache.org/schema/spring">
          <route id="message-tokenizer">
            <from uri="direct:messages"/>
            <split>
              <tokenize token=" "/>
              <to uri="seda:words"/>
            </split>
          </route>
        </routeContext>
      </camel:create-routes>
  </actions>
</testcase>

In the example above we have used the camel:create-route test action that will create new Camel routes at runtime in the Camel context. The target Camel context is referenced with an automatic context lookup.

Note
The default Camel context name in this lookup is "citrusCamelContext". You can set this default context name via environment variables (CITRUS_CAMEL_CONTEXT_NAME) or system properties (citrus.camel.context.name).

If no specific settings are set Citrus will automatically try to look up the Camel context with name "citrusCamelContext" in the Spring bean configuration. All route operations will target this Camel context then.

In addition to that you can skip this lookup and directly reference a target Camel context with the action attribute camel-context (used in the second action above).

Remove Camel routes

You can remove routes from the Camel context as part of the test.

Java
public class CamelRouteActionIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void removeCamelRoutes() {
        $(camel().camelContext(camelContext)
            .route()
            .remove("route_1", "route_2", "route_3"));
    }
}
XML
<testcase name="CamelRouteIT">
  <actions>
      <camel:remove-routes camel-context="camelContext">
        <route id="route_1"/>
        <route id="route_2"/>
        <route id="route_3"/>
      </camel:remove-routes>
  </actions>
</testcase>

Start/stop routes

Next operation we will discuss is the start and stop of existing Camel routes:

Java
public class CamelRouteActionIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void startThenStopCamelRoutes() {
        $(camel().camelContext(camelContext)
            .route()
            .start("route_1"));

        $(camel().camelContext(camelContext)
            .route()
            .stop("route_2", "route_3"));
    }
}
XML
<testcase name="CamelRouteIT">
  <actions>
      <camel:start-routes camel-context="camelContext">
        <route id="route_1"/>
      </camel:start-routes>

      <camel:stop-routes camel-context="camelContext">
        <route id="route_2"/>
        <route id="route_3"/>
      </camel:stop-routes>
  </actions>
</testcase>

Starting and stopping Camel routes at runtime is important when temporarily Citrus need to receive a message on a Camel endpoint URI. We can stop a route, use a Citrus camel endpoint instead for validation and start the route after the test is done. This way we can also simulate errors and failure scenarios in a Camel route interaction.

Camel controlbus actions

The Camel controlbus component is a good way to access route statistics and route status information within a Camel context. Citrus provides controlbus test actions to easily access the controlbus operations at runtime.

Java
public class CamelControlBusIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void createCamelRoute() {
        $(camel().camelContext(camelContext)
            .controlbus()
            .route("route_1")
            .status()
            .result(ServiceStatus.Stopped));

        $(camel().camelContext(camelContext)
            .controlbus()
            .route("route_1")
            .start());

        $(camel().camelContext(camelContext)
            .controlbus()
            .route("route_1")
            .status()
            .result(ServiceStatus.Started));
    }
}
XML
<testcase name="CamelControlBusIT">
  <actions>
    <camel:control-bus camel-context="camelContext">
      <camel:route id="route_1" action="status"/>
      <camel:result>Stopped</camel:result>
    </camel:control-bus>

    <camel:control-bus>
      <camel:route id="route_1" action="start"/>
    </camel:control-bus>

    <camel:control-bus camel-context="camelContext">
      <camel:route id="route_1" action="status"/>
      <camel:result>Started</camel:result>
    </camel:control-bus>

    <camel:control-bus>
      <camel:language type="simple">${camelContext.stop()}</camel:language>
    </camel:control-bus>

    <camel:control-bus camel-context="camelContext">
      <camel:language type="simple">${camelContext.getRouteController().getRouteStatus('route_3')}</camel:language>
      <camel:result>Started</camel:result>
    </camel:control-bus>
  </actions>
</testcase>

The example test case shows the controlbus access. As already mentioned you can explicitly reference a target Camel context with camel-context="camelContext". In case no specific context is referenced Citrus will automatically lookup a target Camel context with the default context name "citrusCamelContext".

Camel provides two different ways to specify operations and parameters. The first option is the use of an action attribute. The Camel route id has to be specified as mandatory attribute. As a result the controlbus action will be executed on the target route during test runtime. This way we can also start and stop Camel routes in a Camel context.

In case a controlbus operation has a result such as the status action we can specify a control result that is compared. Citrus will raise validation exceptions when the results differ.

The second option for executing a controlbus action is the language expression. We can use Camel language expressions on the Camel context for accessing a controlbus operation. Also, here we can define an optional outcome as expected result.

Java
public class CamelControlBusIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void createCamelRoute() {
        $(camel().camelContext(camelContext)
            .controlbus()
            .language("simple", "${camelContext.getRouteStatus('my_route')}")
            .result(ServiceStatus.Stopped));

        $(camel().camelContext(camelContext)
            .controlbus()
            .language("simple", "${camelContext.stop()}"));
    }
}
XML
<testcase name="CamelControlBusIT">
  <actions>
    <camel:control-bus camel-context="camelContext">
      <camel:language type="simple">${camelContext.getRouteStatus('my_route')}</camel:language>
      <camel:result>Started</camel:result>
    </camel:control-bus>

    <camel:control-bus>
      <camel:language type="simple">${camelContext.stop()}</camel:language>
    </camel:control-bus>
  </actions>
</testcase>

Camel CLI support

The Apache Camel project offers a great CLI tooling that allows you to run Camel integrations directly from the command line. Citrus supports two ways to launch the Camel CLI:

  • JBang (default) — uses JBang to resolve and run the Camel CLI application.

  • Camel Launcher jar — uses a standalone Camel Launcher jar file executed via java -jar.

Camel JBang setup

By default, Citrus uses JBang to launch the Camel CLI. You can install the Camel CLI tooling on your local machine as a JBang application.

jbang app install camel@apache/camel

The --help option will show you all available commands that you can run with Camel CLI.

Camel Launcher setup

As an alternative to JBang you can use the Camel Launcher jar to run Camel CLI commands. The Camel Launcher is a standalone executable jar that does not require JBang to be installed.

To use the Camel Launcher jar, configure the following settings:

System properties
citrus.camel.cli.type=launcher
citrus.camel.cli.launcher.jar.path=/path/to/camel-launcher.jar
Environment variables
export CITRUS_CAMEL_CLI_TYPE=launcher
export CITRUS_CAMEL_CLI_LAUNCHER_JAR_PATH=/path/to/camel-launcher.jar

When configured, Citrus will execute Camel CLI commands as java -jar <path-to-jar> <command> <args> instead of using JBang.

Run Camel integrations

One of the available commands in Camel CLI is the run command that takes a Camel route as an input and runs the route locally without any prior project setup.

Citrus is able to use this capability in a test to run a Camel integration in a separate process.

Given the following Camel route written in YAML:

Sample Camel route.yaml
- from:
    uri: "timer:tick"
    parameters:
      period: "1000"
      includeMetadata: true
    steps:
      - setBody:
          simple: "{{greeting}} #${header.CamelTimerCounter}"
      - to: "log:info"

The Camel route uses a timer component to periodically log a message to the output. The message is given as a property placeholder greeting and may be set in the application.properties.

application.properties
greeting=Hello from Apache Camel!

The Citrus test is able to run this route with Camel CLI in a separate process:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .run()
                .integration(Resources.fromClasspath("route.yaml", CamelCliTest.class))
                .withSystemProperty("greeting", "Hello from Apache Camel!"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        run:
          integration:
            file: "classpath:route.yaml"
            systemProperties:
              properties:
                - name: greeting
                  value: Hello from Apache Camel!
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <run>
          <integration file="classpath:route.yaml">
            <system-properties>
              <property name="greeting" value="Hello from Apache Camel!"/>
            </system-properties>
          </integration>
        </run>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

This starts the Camel integration with Camel CLI. The test waits for the integration to report running status. You can skip the waiting state with the waitForRunningState=false option.

Tip
The test action has an autoRemove option that is enabled by default. This means that the test will automatically stop and remove the Camel integration after the test.
Tip
You may choose to add the --stub option to the Camel CLI run command. This allows you to stub endpoints in the Camel integration route. The stubbed endpoint uses a local internal message queue instead of connecting to the real endpoint. This is very useful when an endpoint raises quite complex connection efforts. For instance, you may stub an SQL endpoint or a message broker connection endpoint such as Kafka or JMS when running the Camel integration. You may access the messages processed by the stubbed endpoint in the test with a cmd receive test action that is described later in this guide.

Stop Camel integrations

You may want to explicitly stop the Camel integration. You can do this with the following action:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .stop()
                .integration("route")
        );
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        stop:
          integration: "route"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <stop integration="route"/>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The Camel integration to stop is identified by its name. Usually this is the file name without the file extension. You can also specify a name when running the integration and reference this name in the stop action.

Verify Camel integrations

Each Camel integration reports a state (running or stopped) that Citrus is able to verify. In addition to that the running Camel integration produces a log output that Citrus is able to scan for expected messages.

You can do all of this in the verify action.

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .verify("route")
                .waitForLogMessage("Hello from Apache Camel! #10"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        verify:
          integration: "route"
          logMessage: "Hello from Apache Camel! #10"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <verify integration="route" log-message="Hello from Apache Camel! #10"/>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The action above verifies that the Camel integration is running. Also, the action waits for a log message Hello from Apache Camel! #10. The action periodically polls the produced log output and searches for the expected log message.

The action provides options such as maxAttempts and pollingInterval to stop the waiting period. In case the log message is not found after the maximum number of attempts the test action fails the test.

Send messages

The Camel CLI tooling allows you to send messages to running Camel integrations. You can use the send command to send messages to any running route.

Give the following Camel integration that is running in Camel CLI:

echo.yaml
- from:
    uri: "direct:echo"
    steps:
      - transform:
          simple: "${body.toUpperCase()}"
      - to: "log:info"

The Camel route listens for messages on the direct:echo endpoint. With Camel CLI you can send messages to this route using the following command:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldSendMessage() {
        when(camel().cli().cmd()
                    .send()
                    .integration("echo")
                    .body("Hello Camel"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
    cli:
      cmd:
        send:
          integration: "echo"
          body:
            data: "Hello Camel"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <cmd>
          <send integration="echo">
            <body>
              <data>Hello Camel</data>
            </body>
          </send>
        </cmd>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The send action connects with the running Camel integration called echo and send the message body Hello Camel to the route. The send operation automatically uses the direct:echo endpoint because there is only one single route.

In case you have multiple routes running with multiple endpoints you can specify the endpoint with the endpoint attribute.

Also, you may set some additional message headers.

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldSendMessage() {
        when(camel().cli().cmd()
                    .send()
                    .integration("echo")
                    .endpoint("direct:echo")
                    .header("user", "christoph")
                    .body("Hello Camel"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
    cli:
      cmd:
        send:
          integration: "echo"
          endpoint: "direct:echo"
          headers:
            user: christoph
          body:
            data: "Hello Camel"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <cmd>
          <send integration="echo" endpoint="direct:echo">
            <headers>
              <header name="user" value="christoph"/>
            </headers>
            <body>
              <data>Hello Camel</data>
            </body>
          </send>
        </cmd>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

You can specify a send timeout with the timeout attribute (by default this is set to 20 seconds).

The reply=true/false option makes the send operation wait for a response message from the Camel integration. The response data is logged to the output.

You can also use the Camel CLI send command without a running Camel integration. This can be used to send messages to any support Camel endpoint. Just use the endpoint uri setting on the send operation like this:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldSendMessage() {
        when(camel().cli().cmd()
                    .send()
                    .uri("kafka:echo?server=localhost:9092")
                    .header("user", "christoph")
                    .body("Hello Camel"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
    cli:
      cmd:
        send:
          uri: "kafka:echo?server=localhost:9092"
          headers:
            user: christoph
          body:
            data: "Hello Camel"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <cmd>
          <send uri="kafka:echo?server=localhost:9092">
            <headers>
              <header name="user" value="christoph"/>
            </headers>
            <body>
              <data>Hello Camel</data>
            </body>
          </send>
        </cmd>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>
Tip
You can also use the Kamelets sources to send messages with Camel CLI. The Kamelets provide easy to use properties for comfortable connection options.

Please see the Camel CLI send command help for more configuration options.

Camel CLI cmd send options
camel cmd send --help

Receive messages

The Camel CLI tooling allows you to access stubbed messages in a running Camel integration. You can use the receive command to list messages that an endpoint in Camel has been processing.

Give the following Camel integration that is running in Camel CLI:

echo.yaml
- from:
    uri: "direct:echo"
    steps:
      - transform:
          simple: "${body.toUpperCase()}"
      - to: "kafka:inbox?brokers=localhost:9092"

The Camel route listens for messages on the direct:echo endpoint and connects with a Kafka message broker. With Camel CLI you can start the route with a stubbed Kafka endpoint like this:

Run Camel integration with --stub option
camel run my-route.yaml --stub=kafka

This makes Camel use an internal message queue instead of connecting with the Kafka message broker. The messages received by the stubbed Kafka component in Camel can be accessed with the receive command:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldSendMessage() {
        then(camel().cli().cmd()
                    .receive()
                    .integration("my-route")
                    .tail(1)
                    .endpoint("kafka:inbox"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        cmd:
          receive:
            integration: "my-route"
            tail: 1
            endpoint: "kafka:inbox"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <cmd>
          <receive integration="my-route" tail="1" endpoint="kafka:inbox"/>
        </cmd>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The receive action connects with the running Camel integration called my-route and receives messages on the stubbed kafka:inbox endpoint. The tail setting defines how many messages that have been received before will be included. This means a setting of tail=1 includes the last message that was processed by the stubbed endpoint. By default, the operation will wait only for new messages to arrive (tail=0).

The test action monitors the receive command output and waits for messages to arrive. You can use the grep option to wait for very specific messages that match the given expression. Also, you can specify the since option that allows you to include messages that have arrived in a given time span (e.g. 5s, 2m, 1h)

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldSendMessage() {
        then(camel().cli().cmd()
                    .receive()
                    .integration("my-route")
                    .tail(1)
                    .since("1h")
                    .grep("HELLO CAMEL")
                    .endpoint("kafka:inbox"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        cmd:
          receive:
            integration: "my-route"
            tail: 1
            since: 1h
            grep: HELLO CAMEL
            endpoint: "kafka:inbox"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <cmd>
          <receive integration="my-route"
                    tail="1"
                    since="1h"
                    grep="HELLO CAMEL"
                    endpoint="kafka:inbox"/>
        </cmd>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The grep, tail and since options help you to verify that a specific message has been processed on the stubbed endpoint. The test action polls the command output for the message and throws a timeout error in case the message does not arrive in time. You can use the maxAttempts and delayBetweeAttempts settings to specify the waiting and polling configuration.

Please see the Camel CLI receive command help for more configuration options.

Camel CLI cmd receive options
camel cmd receive --help

Manage Camel plugins

Camel CLI supports plugins that extend the CLI with additional commands and capabilities. Citrus provides test actions to add and delete plugins as part of a test.

Add a plugin

Java
when(camel().cli()
    .plugin()
    .add()
    .pluginName("my-plugin")
    .withArg("--version", "1.0.0"));
YAML
- camel:
    cli:
      plugin:
        add:
          name: "my-plugin"
          args:
            - "--version"
            - "1.0.0"
XML
<camel>
  <cli>
    <plugin>
      <add name="my-plugin" args="--version 1.0.0"/>
    </plugin>
  </cli>
</camel>

The action checks if the plugin is already installed by name. When the plugin is already installed and no --version or --gav arguments are provided, the action skips the installation. When --version or --gav arguments are present, the action removes the existing plugin and reinstalls it with the new arguments.

Tip
The test action has an autoRemove option that is disabled by default. When enabled, the test will automatically delete the plugin after the test using the context.doFinally() mechanism. You can enable it globally via the citrus.camel.cli.auto.remove.plugins system property or CITRUS_CAMEL_CLI_AUTO_REMOVE_PLUGINS environment variable.

Delete a plugin

You can explicitly delete a plugin in your test. This is useful in finally blocks to clean up plugins that were installed during the test.

Java
when(camel().cli()
    .plugin()
    .delete()
    .pluginName("my-plugin"));
YAML
- camel:
    cli:
      plugin:
        delete:
          name: "my-plugin"
XML
<camel>
  <cli>
    <plugin>
      <delete name="my-plugin"/>
    </plugin>
  </cli>
</camel>

Camel CLI settings

The Camel CLI support uses the following environment settings. Settings use the citrus.camel.cli. prefix. The older citrus.camel.jbang. prefix is still supported as a fallback.

Table 1. System properties
System property Description

citrus.camel.cli.type

The launcher type to use: jbang (default) or launcher. When set to launcher, Citrus uses the Camel Launcher jar instead of JBang.

citrus.camel.cli.launcher.jar.path

Path to the Camel Launcher jar file. Required when citrus.camel.cli.type is set to launcher. Falls back to the camel.launcher.jar system property.

citrus.camel.cli.app

Camel JBang app name. (default="camel@apache/camel")

citrus.camel.cli.version

Camel version to use. (default="latest")

citrus.camel.cli.work.dir

Local working directory for the Camel CLI.

citrus.camel.cli.kamelets.version

Kamelets version used by the Camel CLI runtime.

citrus.camel.cli.kamelets.local.dir

Local Kamelets directory.

citrus.camel.cli.dump.integration.output

When set to true, Camel CLI process output for integrations will be redirected to a file in the current working directory. (default="false")

citrus.camel.cli.verbose

When set to true, Camel CLI will print detailed messages. (default="false")

citrus.camel.cli.auto.remove.resources

When set to true, Camel CLI resources created during the test are automatically removed after the test. (default="true")

citrus.camel.cli.auto.remove.plugins

When set to true, Camel CLI plugins added during the test are automatically removed after the test. (default="false")

citrus.camel.cli.wait.for.running.state

When set to true, Camel CLI will automatically wait for each integration created to be in running state. (default="true")

citrus.camel.cli.trust.url

Comma-separated list of trust URLs for JBang. (default="https://github.com/apache/camel/")

Table 2. Environment variables
Environment variable Description

CITRUS_CAMEL_CLI_TYPE

The launcher type to use: jbang (default) or launcher.

CITRUS_CAMEL_CLI_LAUNCHER_JAR_PATH

Path to the Camel Launcher jar file.

CITRUS_CAMEL_CLI_APP

Camel JBang app name.

CITRUS_CAMEL_CLI_VERSION

Camel version to use.

CITRUS_CAMEL_CLI_WORK_DIR

Local working directory for the Camel CLI.

CITRUS_CAMEL_CLI_KAMELETS_VERSION

Kamelets version used by the Camel CLI runtime.

CITRUS_CAMEL_CLI_KAMELETS_LOCAL_DIR

Local Kamelets directory.

CITRUS_CAMEL_CLI_DUMP_INTEGRATION_OUTPUT

When set to true, integration output is redirected to a file.

CITRUS_CAMEL_CLI_VERBOSE

When set to true, Camel CLI will print detailed messages.

CITRUS_CAMEL_CLI_AUTO_REMOVE_RESOURCES

When set to true, resources are automatically removed after the test.

CITRUS_CAMEL_CLI_AUTO_REMOVE_PLUGINS

When set to true, plugins are automatically removed after the test.

CITRUS_CAMEL_CLI_WAIT_FOR_RUNNING_STATE

When set to true, waits for integrations to reach running state.

CITRUS_CAMEL_CLI_TRUST_URL

Comma-separated list of trust URLs for JBang.

Camel CLI Kubernetes support

The Camel CLI integrations are run locally by default as a separate JBang process. In addition to that you may also run the integration in a Kubernetes namespace.

This uses the Camel CLI Kubernetes plugin that is able to build a container image and deploy it to a connected Kubernetes namespace.

Deploy the Camel integration

The Camel CLI Kubernetes plugin is able to build a container image from an integration and deploy the integration with a generated Kubernetes manifest.

Note
The following test actions call the Camel Kubernetes plugin. A prerequisite to this is that you are connected to a Kubernetes cluster and a namespace. It is essential to have a Kubernetes command line tooling installed and ready for usage on your local machine.

Citrus provides a test action to call the Camel CLI Kubernetes plugin:

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .kubernetes()
                .run()
                .integration(Resources.fromClasspath("route.yaml", CamelCliTest.class))
                .runtime("quarkus")
                .clusterType("kind")
                .imageBuilder("docker")
                .imageRegistry("localhost:5000")
                .withProperty("greeting=Hello from Apache Camel!")
        );
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
       kubernetes:
          run:
            integration:
              file: "classpath:route.yaml"
            runtime: "quarkus"
            clusterType: "kind"
            imageBuilder: "docker"
            imageRegistry: "localhost:5000"
            properties:
              - name: greeting
                value: Hello from Apache Camel!
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <kubernetes>
          <run runtime="quarkus"
               cluster-type="kind"
               image-builder="docker"
               image-registry="localhost:5000">
            <integration file="classpath:route.yaml"/>
            <properties>
              <property name="greeting" value="Hello from Apache Camel!"/>
            </properties>
          </run>
        </kubernetes>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The Kubernetes run action receives detailed configuration on how to build the container image and where to push the resulting image. You may choose the target cluster type and use an external or internal image registry (e.g. quay.io or localhost:5000).

The Camel CLI Kubernetes plugin will perform several steps to create the deployment. First of all the plugin creates a project export for the given Camel integration and builds the container image.

Also, the plugin generates a Kubernetes manifest that holds all required resources to deploy the integration to a Kubernetes cluster. The Kubernetes deployment is automatically performed as a last step in this action.

The result will be a new Kubernetes deployment in the current namespace that uses the freshly built container image.

Important
Please make sure to connect to a Kubernetes cluster and choose a proper namespace before running the test action. The Kubernetes client will automatically use the current cluster and namespace.
Tip
The action has an autoRemove setting that is enabled by default. This means that the Kubernetes deployment is automatically stopped and removed after the test.

Delete integrations from Kubernetes

You can explicitly remove the Camel integration from Kubernetes.

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .kubernetes()
                .delete()
                .integration("route")
        );
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        kubernetes:
          delete:
            integration: "route"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <kubernetes>
          <delete>
            <integration name="route"/>
          </delete>
        </kubernetes>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

This deletes the Kubernetes deployment and all resources listed in the Camel integration Kubernetes manifest.

Verify integration on Kubernetes

Citrus is able to verify the state of a running Camel integration in Kubernetes. This is the status of the Kubernetes deployment and its pods and containers. Also, Citrus is able to scan the log output of a Camel integration that is running in Kubernetes.

Java
public class CamelCliTest extends TestNGCitrusSupport implements TestActionSupport {

    @Test
    @CitrusTest
    public void shouldRunCamelCli() {
        when(camel().cli()
                .kubernetes()
                .verify("route")
                .waitForLogMessage("Hello from Apache Camel! #10"));
    }
}
YAML
name: "CamelCliTest"
actions:
  - camel:
      cli:
        kubernetes:
          verify:
            integration: "route"
            logMessage: "Hello from Apache Camel! #10"
XML
<test name="CamelCliTest" xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <cli>
        <kubernetes>
          <verify integration="route" log-message="Hello from Apache Camel! #10"/>
        </kubernetes>
      </cli>
    </camel>
  </actions>
</test>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

The action makes sure to find the Kubernetes deployment and the running Pod. The action waits for the expected log message and fails when the message is not available in the log output after the maximum number of attempts.

Camel infrastructure services

The Apache Camel project maintains a huge set of test infrastructure services that you can also start and stop during a Citrus test. See this documentation about the Camel test infra services.

The Camel infra services usually start Docker/Podman containers and/or use Testcontainers.

See this list of supported infrastructure services in Apache Camel:

Service Service Service

arangodb

artemis (amqp, mqtt, persistent)

aws (dynamodb, event-bridge, kinesis, s3, sns, sqs, sts, …​)

azure

cassandra

chat-script

couchbase

couchdb

elasticsearch

fhir

ftp

ftps

google (pub-sub)

hashicorp (vault)

hazelcast

hive-mq (sparkplug)

kafka (confluent, redpanda, strimzi)

microprofile (lra)

milvus

minio

mongodb

mosquitto

nats

ollama

openldap

postgres

pulsar

qdrant

rabbitmq

redis

rocketmq

sftp

smb

solr

torch-serve

xmpp

zookeeper

You can start/stop such an infra service via test actions in Citrus.

Run Camel infra services

Java
public class CamelInfraIT extends TestNGCitrusSpringSupport {

    @Test
    @CitrusTest
    public void createInfraService() {
        $(camel()
            .infra()
            .run()
            .service("postgres"));
    }
}
YAML
name: "CamelInfraTest"
actions:
  - camel:
      infra:
        run:
          service: postgres
XML
<test name="CamelInfraIT"
      xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <infra>
        <run service="postgres"/>
      </infra>
    </camel>
  </actions>
</testcase>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

Once the infrastructure service is started Citrus exposes connection settings as test variables. You can use the exposed connection settings to create proper clients that connect to the services.

The exposed connection settings follow a naming pattern that looks like this:

  • CITRUS_CAMEL_INFRA_SERVICE_<SERVICE_NAME>_<PROPERTY_NAME>

  • CITRUS_CAMEL_INFRA_SERVICE_<SERVICE_NAME>_<IMPLEMENTATION>_<PROPERTY_NAME>

For instance a infrastructure service called foo that exposes the settings url and port expose the following test variables.

Variable

CITRUS_CAMEL_INFRA_SERVICE_FOO_URL

CITRUS_CAMEL_INFRA_SERVICE_FOO_PORT

You can now use these test variables when connecting to the service. Please see the individual Apache Camel infrastructure services documentation to see which properties get exposed by the service.

Some infrastructure services provide multiple implementations (e.g. Kafka provides both Redpanda and Strimzi).

You may need to choose the implementation as follows:

Java
public class CamelInfraIT extends TestNGCitrusSpringSupport {

    @Test
    @CitrusTest
    public void createInfraService() {
        $(camel()
            .infra()
            .run()
            .service("kafka")
            .implementation("strimzi"));
    }
}
YAML
name: "CamelInfraTest"
actions:
  - camel:
      infra:
        run:
          service: kafka
          implementation: strimzi
XML
<test name="CamelInfraIT"
      xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <infra>
        <run service="kafka" implementation="strimzi"/>
      </infra>
    </camel>
  </actions>
</testcase>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

By default, the infrastructure services get automatically removed after the test. You can disable the auto removal when running the service (auto-remove setting).

Fixed port configuration

By default, Camel infra services use random ports for better test isolation. In some cases you may want to use a fixed port instead (e.g. when an external tool expects a service on a well-known port, or when debugging locally).

You can configure a fixed port directly on the run action:

Java
public class CamelInfraIT extends TestNGCitrusSpringSupport {

    @Test
    @CitrusTest
    public void createInfraService() {
        $(camel()
            .infra()
            .run()
            .service("postgres")
            .port(5432));
    }
}
YAML
name: "CamelInfraTest"
actions:
  - camel:
      infra:
        run:
          service: postgres
          port: 5432

Setting a specific port automatically enables fixed port mode. You can also enable fixed port mode without specifying a concrete port number — this tells the service to use its own default port rather than a random one:

Java
$(camel()
    .infra()
    .run()
    .service("postgres")
    .fixedPort(true));
YAML
name: "CamelInfraTest"
actions:
  - camel:
      infra:
        run:
          service: postgres
          fixedPort: true

The fixed port settings can also be configured globally via system properties or environment variables (see Camel infra settings).

Stop Camel infra services

To explicitly stop a running infrastructure service use the following test action.

Java
public class CamelInfraIT extends TestNGCitrusSpringSupport {

    @Test
    @CitrusTest
    public void createInfraService() {
        $(camel()
            .infra()
            .stop()
            .service("postgres"));
    }
}
YAML
name: "CamelInfraTest"
actions:
  - camel:
      infra:
        stop:
          service: postgres
XML
<test name="CamelInfraIT"
      xmlns="http://citrusframework.org/schema/xml/testcase"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd">
  <actions>
    <camel>
      <infra>
        <stop service="postgres"/>
      </infra>
    </camel>
  </actions>
</testcase>
Spring XML
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
              xmlns:spring="http://www.springframework.org/schema/beans">
    <!-- NOT SUPPORTED -->
</spring:beans>

Camel infra settings

The Camel infrastructure services support the following environment settings.

Table 3. System properties
System property Description

citrus.camel.infra.auto.remove.services

When set to true the test will automatically stop and remove Camel infra services that have been started. (default="true")

citrus.camel.infra.dump.service.output

When set to true infra service output will be redirected to a file in the current working directory. (default="false")

citrus.camel.infra.port

When set infra services use this port as a fixed port. (default="-1", meaning random port)

citrus.camel.infra.fixedPort

When set to true infra services use fixed ports. Otherwise, services use random ports for better isolation. (default="false")

citrus.camel.infra.<service>.port

Sets a fixed port for a specific service. The service name is the full service name (e.g. citrus.camel.infra.postgres.port=5432).

Table 4. Environment variables
Environment variable Description

CITRUS_CAMEL_INFRA_AUTO_REMOVE_SERVICES

When set to true the test will automatically stop and remove Camel infra services that have been started. (default="true")

CITRUS_CAMEL_INFRA_DUMP_SERVICE_OUTPUT

When set to true infra service output will be redirected to a file in the current working directory. (default="false")

CITRUS_CAMEL_INFRA_PORT

When set infra services use this port as a fixed port. (default="-1", meaning random port)

CITRUS_CAMEL_INFRA_FIXED_PORT

When set to true infra services use fixed ports. Otherwise, services use random ports for better isolation. (default="false")

CITRUS_CAMEL_INFRA_<SERVICE>_PORT

Sets a fixed port for a specific service. The service name uses upper case and non-alphanumeric characters are replaced with underscores (e.g. CITRUS_CAMEL_INFRA_POSTGRES_PORT=5432).

Camel endpoint DSL

Since Camel 3 the endpoint DSL provides a convenient way to construct an endpoint uri. In Citrus you can use the Camel endpoint DSL to send/receive messages in a test.

Java
$(send(camel().endpoint(seda("test")::getRawUri))
        .message()
        .body("Citrus rocks!"));

The fluent endpoint DSL in Camel allows to build the endpoint uri. The camel().endpoint(seda("test")::getRawUri) builds the endpoint uri seda:test. The endpoint DSL provides all settings and properties that you can set for a Camel endpoint component.

Camel processor support

Camel implements the concept of processors as enterprise integration pattern. A processor is able to add custom logic to a Camel route. Each processor is able to access the Camel exchange that is being processed in the current route. The processor is able to change the message content (body, headers) as well as the exchange information.

The send/receive operations in Citrus also implement the processor concept. With the Citrus Camel support you can use the very same Camel processor also in a Citrus test action.

Message processor on send
public class CamelMessageProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessMessages() {
        $(send(camel().endpoint(seda("test")::getRawUri))
                .message()
                .body("Citrus rocks!")
                .process(processor()
                    .camel()
                    .camelContext(camelContext)
                    .process(exchange -> exchange
                            .getMessage()
                            .setBody(exchange.getMessage().getBody(String.class).toUpperCase())))
        );
    }
}

The example above uses a Camel processor to change the exchange and the message content before the message is sent to the endpoint. This way you can apply custom changes to the message as part of the test action.

Message processor on receive
public class CamelMessageProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessMessages() {
        $(send(camel().endpoint(seda("test")::getRawUri))
                .message()
                .body("Citrus rocks!"));

        $(receive(camel().endpoint(seda("test")::getRawUri))
                .process(processor()
                        .camel(camelContext)
                        .process(exchange -> exchange
                                .getMessage()
                                .setBody(exchange.getMessage().getBody(String.class).toUpperCase())))
                .message()
                .type(MessageType.PLAINTEXT)
                .body("CITRUS ROCKS!"));
    }
}

The Camel processors are very powerful. In particular, you can apply transformations of multiple kind.

Transform processor
public class CamelTransformIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldTransformMessageReceived() {
        $(send(camel().endpoint(seda("hello")::getRawUri))
                .message()
                .body("{\"message\": \"Citrus rocks!\"}")
        );

        $(receive(camel().endpoint(seda("hello")::getRawUri))
                .transform(
                    camel()
                        .camelContext(camelContext)
                        .transform()
                        .jsonpath("$.message"))
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Citrus rocks!"));
    }
}

The transform pattern is able to change the message content before a message is received/sent in Citrus. The example above applies a JsonPath expression as part of the message processing. The JsonPath expression evaluates $.message on the Json payload and saves the result as new message body content. The following message validation expects the plaintext value Citrus rocks!.

The message processor is also able to apply a complete route logic as part of the test action.

Route processor
public class CamelRouteProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessRoute() {
        CamelRouteProcessor.Builder beforeReceive = camel(camelContext).route(route ->
                route.choice()
                    .when(jsonpath("$.greeting[?(@.language == 'EN')]"))
                        .setBody(constant("Hello!"))
                    .when(jsonpath("$.greeting[?(@.language == 'DE')]"))
                        .setBody(constant("Hallo!"))
                    .otherwise()
                        .setBody(constant("Hi!")));

        $(send(camel().endpoint(seda("greetings")::getRawUri))
                .message()
                .body("{" +
                        "\"greeting\": {" +
                            "\"language\": \"EN\"" +
                        "}" +
                      "}")
        );

        $(receive("camel:" + camel().endpoints().seda("greetings").getUri())
                .process(beforeReceive)
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Hello!"));
    }
}

With the complete route logic you have the full power of Camel ready to be used in your send/receive test action. This enables many capabilities as Camel implements the enterprise integration patterns such as split, choice, enrich and many more.

Camel data format support

Camel uses the concept of data format to transform message content in form of marshal/unmarshal operations. You can use the data formats supported in Camel in Citrus, too.

Data format marshal/unmarshal
public class CamelDataFormatIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldApplyDataFormat() {
        when(send(camel().endpoint(seda("data")::getRawUri))
                .message()
                .body("Citrus rocks!")
                .transform(camel(camelContext)
                        .marshal()
                        .base64())
        );

        then(receive("camel:" + camel().endpoints().seda("data").getUri())
                .transform(camel(camelContext)
                        .unmarshal()
                        .base64())
                .transform(camel(camelContext)
                        .convertBodyTo(String.class))
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Citrus rocks!"));
    }
}

The example above uses the base64 data format provided in Camel to marshal/unmarshal the message content to/from a base64 encoded String. Camel provides support for many data formats as you can see in the documentation on data formats.