Apache Camel and testing configured routes

Apache Camel is the Open Source offering for an Enterprise level integration framework that any company can adopt as their integration platform easily.

The heart of Camel (not the animal) is its easy to use Java DSL programming syntax that lets you avoid the awful XML based route configuration. One of the best practices for configuring a route in Camel is to use one of the Enterprise Integration Patterns (EIP). You will often see EIP being referred in many of the tutorials online. EIPs are nothing but “enterprise like” components that let you transform, and route a message in the system as per your needs.

For more information about EIPs, you may refer to http://camel.apache.org/enterprise-integration-patterns.html.

In my company, we heavily rely on Camel for sending messages between different modules (or processes). Each module contributes to the message in its own way and hence it is very important to have an EIP that will keep the message’s integrity through its lifecycle while it is in the system.

Coming from an enterprise background, I found it easy to develop in Camel. I used to work for an insurance company in Jersey City, NJ and they are a huge ‘IBM shop’. All of our enterprise software was IBM products. Websphere MQ, WAS 6.1/7.0, WPS 7.0 so I had the opportunity to work with Websphere Integration Developer to develop Mediations and Process Models that run on the IBM ESB server called the Process Server. It was a beast in itself.

Anyway, unit testing Websphere mediations and process models are very easy. There is built in support to create ‘test projects’ and it’s as simple as few mouse clicks and then you will have to enter some test data to run the test. For Camel, it is a bit different.

Here’s how it is done. Camel is shipped with support for unit testing with an extendable class called CamelTestSupport. To inject mock endpoints for your test, you could declare the mock endpoints as class members. It is not necessary to declare them as class members but it is a good idea to do so which will allow you to reuse them in different test methods.

@EndpointInject(uri = “result”) protected MockEndpoint resultEndpoint;

In your test method, you can set the expected message count on this mock endpoint like this:

//set the expected message count for the result endpoint

MockEndpoint mock = getMockEndpoint(“mock:result”);

mock.setExpectedMessageCount(1);

You can then use the expected message count to assert the status at the end of your test method. Now how do you “inject” this endpoint to your already configured production. If you are a good developer, you wouldn’t want to change your production route just to inject a mock endpoint. Before you ask me what is wrong in changing my production route to include a mock endpoint to make things easy to test, what if your production route was an ActiveMQ or some other endpoint? Exactly! So, let’s see how one can work around this.

//add interceptors to inject mock endpoints to assert expected message counts

try {

//first find the endpoint you want to intercept

List<RouteDefinition> routeDefinitions = context.getRouteDefinitions();

RouteDefinition queryCache = null;

RouteDefinition rd = null;

List<FromDefinition> fromDefinitions = null;

FromDefinition fd = null;

for(int i = 0 ; i < routeDefinitions.size() ; i++){

rd = routeDefinitions.get(i);

fromDefinitions = rd.getInputs();

fd = fromDefinitions.get(0);

if(fd.getUri().equals(“direct:someproductionendpoint”))

queryCache = rd;

}

//add the interceptor to the endpoint now

if(queryCache != null){

queryCache.adviceWith(context, new RouteBuilder() {

@Override

    public void configure() throws Exception {

        // intercept sending to direct:someproductionendpoint

        interceptSendToEndpoint(“direct:someproductionendpoint”)

                .to(“mock:result”);

    }

});

}

}

catch(Exception e){

System.out.println(“Problem adding interceptors”);

}

Interceptors! Yes, that’s what I used to introduce mock endpoints in a production route which would allow me to inspect the message flow in the route and assert my test method status accordingly. To learn more about interceptors, see http://camel.apache.org/intercept.html

Sometimes you may also want to completely skip an endpoint in your production route and simply want to make sure a message was received intended for that particular endpoint, you can modify the above code to this:

// intercept sending to direct:someproductionendpoint

interceptSendToEndpoint(“direct:someproductionendpoint”)

.skipSendToOriginalEndpoint()

.to(“mock:result”);

To assert if the condition was satisfied, add this to the end:

mock.assertIsSatisfied();