Book Image

Instant Apache Camel Message Routing

By : Bilgin Ibryam
Book Image

Instant Apache Camel Message Routing

By: Bilgin Ibryam

Overview of this book

With new APIs and technologies emerging every day, the need for integrating applications is greater than ever before. With the right tools, integrating applications is not hard. Apache Camel is the leading open source integration and message orchestration framework. Apache Camel, which has a variety of connectors and features numerous well-known integration patterns, has an enormous advantage over home grown integration solutions. Instant Apache Camel Message Routing helps you to get started using the Camel routing engine and Enterprise Integration Patterns. This book will show you how to create integration applications using Apache Camel. You will learn how Camel works and how to leverage the Enterprise Integration Patterns for message routing. Instant Apache Camel Message Routing is a practical and step-by-step guide to Apache Camel and integration patterns. This book will show you how Apache Camel works and how it integrates disparate systems using Enterprise Integration Patterns. The book starts with a high level overview of the Camel architecture before diving into message routing principles. Then, it introduces a number of patterns, complete with diagrams, common use cases, and examples about how to use them with Camel. The book also shows you how to test and monitor Camel applications and cope with failure scenarios.
Table of Contents (7 chapters)

Testing the messaging applications (Advanced)


Integration applications are asynchronous, heterogeneous, and message driven in nature. Traditionally testing such applications is challenging, especially when there is not good tooling support. As a consequence, most of the testing is done manually at the end of the project with or without very little automated tests. Fortunately, Camel offers a variety of helper tools and makes writing routing tests a pleasurable activity. It can run routes isolated in a test container, mock external systems, specify expectations, trigger events, match expectations, simulate load or certain behavior, and so on. Let's see how to test a route written in Java DSL and then the additional tools and techniques used with Camel.

Getting ready

The complete source code for this tutorial is located under the following project: camel-message-routing-examples/testing-routes.

We will use JUnit, but there is also TestNG support, although it has fewer features.

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit-version}</version>
    <scope>test</scope>
</dependency>

For testing applications using Java DSL we need the camel-test dependency:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test</artifactId>
    <version>${camel-version}</version>
    <scope>test</scope>
</dependency>

How to do it...

  1. Let's assume that we want to test the following route defined in Java DSL:

    public class SimpleChoiceRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("direct:start")
            .choice()
                .when(body().isEqualTo("orange"))
                    .to("mock:oranges")
                .when(body().isEqualTo("apple"))
                    .to("mock:apples");}
    }
  2. To test this route we have to extend the abstract CamelTestSupport class in our test and instantiate the route we want to test in the createRouteBuilder method:

    public class SimpleChoiceRouteTest extends CamelTestSupport {
        @Override
        protected RouteBuilder createRouteBuilder() throws Exception {
            return new SimpleChoiceRoute();
        }
    }
  3. For each test, CamelTestSupport will create a CamelContext, run our route under test, and then tear everything down at the end of the test. Let's add our first test:

    @Test
    public void sendsAnAppleMessage() throws Exception {
        MockEndpoint mockOranges = getMockEndpoint("mock:oranges");
        MockEndpoint mockApples = getMockEndpoint("mock:apples");
        mockOranges.setExpectedMessageCount(0);
        mockApples.setExpectedMessageCount(1);
    
        template.sendBody("direct:start", "apple");
        mockOranges.assertIsSatisfied();
        mockApples.assertIsSatisfied();
    }
  4. The test method first gets hold of the mock endpoint and sets the expected number of messages, then using producerTemplate triggers an event and finally verifies that the expectation is matched. This is the general expect-run-verify test pattern. To make the test more readable, we could do a little bit of refactoring. For example, use annotations and declare MockEndpoints as instance variables, specify default Endpoint for ProducerTemplate, assert all mock endpoints in once step, and so on:

    @Produce(uri = "direct:start")
    protected ProducerTemplate start;
    
    @EndpointInject(uri = "mock:oranges")
    private MockEndpoint mockOranges;
    
    @EndpointInject(uri = "mock:apples")
    private MockEndpoint mockApples;
    
    @Test
    public void orderSomeFruits() throws Exception {
        mockOranges.expectedBodiesReceived("orange");
        mockApples.expectedBodiesReceived("apple");
    
        start.sendBody("orange");
        start.sendBody("apple");
        assertMockEndpointsSatisfied();
    }

How it works...

CamelTestSupport is the base class for testing routes. During the setup stage, it will create a new CamelContext, producerTemplate, consumerTemplate, load the routes defined in the test class and start them. Then, in our test, we can access mock endpoints, set expectations and trigger events or directly send messages to the route endpoints using producerTemplate. At the end of each test method CamelTestSupport will stop the routes and CamelContext (unless it is configured to tear down everything at the end of all tests in the test class).

The base class also provides lots of other helpful methods and hooks to customize the test lifecycle. For example, in some cases it is required to set expectations or do some work before starting the routes. That is possible by overriding the isUseAdviceWith method to return true which will prevent CamelContext from starting automatically and it has to be started manually as part of tests:

@Override
public boolean isUseAdviceWith() {
    return true;
}
public void pollsFilesOnStart() throws Exception {
    getMockEndpoint("mock:result").expectedBodiesReceived("Some file content");
    camelContext.start();
    assertMockEndpointsSatisfied();
}

Mock endpoint (http://camel.apache.org/mock.html) is another helpful tool used for testing routes. It is similar to other mocking libraries such as Mockito and jMock, and represents an inmemory list that collects all Exchanges it interacts with. It has mainly two types of methods: for specifying expectations and for verifying them, but it can also simulate specific behavior:

mockReplying.whenAnyExchangeReceived(new Processor() { 
    @Override
    public void process(Exchange exchange) throws Exception {
        Message in = exchange.getIn();
        in.setBody("Mock response: " + in.getBody());
    }
});

The previous code snippet configures mockReplying to modify the message body when an Exchange is received in order to simulate the behavior of the mocked endpoint.

There's more...

Testing Spring XML DSL is no different, apart from using the Spring's ApplicationContext for creating the routes. Here, we will have a look at that and a few other testing tools.

Testing applications written in Spring XML DSL

To test Camel applications written in XML DSL, the camel-test-spring dependency is needed.

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test-spring</artifactId>
    <version>${camel-version}</version>
    <scope>test</scope>
</dependency>

The main difference to testing Java routes is that, instead of extending CamelTestSupport, the test class has to extend CamelSpringTestSupport and override createApplicationContext, rather than createRouteBuilder:

public class SimpleChoiceRouteSpringTest extends CamelSpringTestSupport {
    @Override
    protected AbstractApplicationContext createApplicationContext() {
        return new ClassPathXmlApplicationContext("META-INF/spring/simple-choice-route-context.xml");
    }
    //the rest of the code is the same as previous
 }

CamelSpringTestSupport provides feature-parity with CamelTestSupport, which means it allows tests to be written in the same style, provides the same protected variables, methods, and honors the same lifecycle. In fact we can use the same instance variables and test method from the previous example to test the Spring XML route.

There is also a way for running tests without extending the CamelSpringTestSupport class, called Enhanced Spring Test Support. In this scenario, the test class has to be annotated with @RunWith(CamelSpringJUnit4ClassRunner.class), and use other annotation to inject Endpoints, CamelContext, and so on, because they are not available as protected fields of the parent class:

@RunWith(CamelSpringJUnit4ClassRunner.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@ContextConfiguration(locations = {"classpath:/META-INF/spring/simple-choice-route-context.xml"})
public class SimpleChoiceRouteEnhancedTest {
    @Autowired
    private CamelContext camelContext;

    @Produce(uri = "direct:start")
    protected ProducerTemplate start;
    //the rest of the class is the same as the example previous
}

The previous tests are mainly testing the message transformations and the routing logic in Camel routes. If the application interacts with external systems, there is also a need for integration testing to verify the interaction points. To see more testing techniques check the tests for the other examples from this book.

Other tools for testing

  • AdviceWith (http://camel.apache.org/advicewith.html): Sometimes, a route has hardcoded Endpoints which makes it hard to test, or it might be necessary to modify part of the route before testing. For these kinds of scenarios Camel provides an Aspect-Oriented-Programing (AOP) model with the name AdviceWith. This feature makes it possible to modify the routes after they are loaded to CamelContext and provides methods to add, remove, and replace endpoints by name or ID.

  • NotifyBuilder (http://camel.apache.org/notifybuilder.html): This allows testing routes without modifying them by building conditional expressions and then testing or waiting for that condition to occur. Good for integration testing with external endpoints.

  • DataSet (http://camel.apache.org/dataset.html): The DataSet component provides a mechanism for easily performing load and soak testing of the system.