Testing routes with fixed endpoints using conditional events

When working with Camel, you may at some point need to test a route that has endpoint URIs hardcoded into it. This tends to be more typical when testing Spring or Blueprint routes.

Consider the following route defined in /META-INF/spring/fixedEndpoints-context.xml:

<route id="modifyPayloadBetweenQueues">
  <from uri="activemq:in"/>
  <transform>
    <simple>Modified: ${body}</simple>
  </transform>
  <to uri="activemq:out"/>
</route>

The route endpoints here make use of the ActiveMQ Component to consume from one queue and publish to another. We would like to test this logic as described in the Testing routes defined in Spring recipe, in a pure unit test without making any changes to the route.

Camel provides you with a notification-based DSL for testing this type of route via the NotifyBuilder. Unlike the adviceWith(..) DSL described in the Testing routes with fixed endpoints using AOP recipe, this approach does not rely on modifying the running route in any way. This is useful when you want to engage the actual endpoint technologies in the test, for example when validating performance, while still validating the routing logic.

As such, NotifyBuilder can be considered as a form of black-box testing where you validate that the outputs match the given inputs without needing to know anything about the internals of the routing. This is in contrast to the testing approaches that we have seen previously that adopt a white-box approach where the internals of the routing logic are implicitly visible to the author of the test.

Getting ready

The Java code for this recipe is located in the org.camelcookbook.examples.testing.notifybuilder package.

How to do it...

In order to do event-based testing in Camel, perform the following steps:

  1. In a test method, instantiate a NotifyBuilder instance associated with the Camel context that you would like to test:
    NotifyBuilder notify = new NotifyBuilder(camelContext)
      .from("activemq:in")
      .whenDone(1)
      .whenBodiesDone("Modified: testMessage")
      .create();
  2. Send a message into the route using the endpoint technology. This is quite different to the approach that we have used so far where we used a Camel ProducerTemplate instance. Now we want to exercise the endpoint's consuming capabilities, whereas the ProducerTemplate approach skips that. In our test, we use a Spring JmsTemplate that is connected to same ActiveMQ broker used by the route:
    jmsTemplate.send("in", new MessageCreator() {
        @Override
        public Message createMessage(Session session) 
            throws JMSException {
          TextMessage textMessage =
              session.createTextMessage("testMessage");
          return textMessage;
        }
      });
  3. Use a standard JUnit assert statement to determine whether the NotifyBuilder instance raises an event within a specified period of time:
    assertTrue(notify.matches(10, TimeUnit.SECONDS));

How it works...

The NotifyBuilder DSL from() statement locates a route by its starting endpoint. The when..() statements tell the builder to send an event when the conditions described have been matched. The create() method completes the construction of the builder, putting it into a state prepared to send events.

The builder's matches() method returns a boolean value that indicates whether the conditions as outlined were satisfied exchanges flowing through the route in the time specified. If the specified time expires without the conditions having been met, matches() will return false; otherwise true will be returned as soon as the conditions are met.

The from(startingEndpoint) syntax can be confusing to maintainers of your code, so a fromRoute(id) method is also provided that makes the intent of the builder somewhat clearer.

The whenDone(number) method requests that an event be raised when at least the specified number of exchanges have been successfully processed through the route. Other alternatives include whenFailed(int), which expects at least the specified number of exceptions to have been thrown, and whenComplete(int), which includes both succeeded and failed exchanges processed. All of these apply to result conditions once the message has fully been processed through a route. The whenReceived(int) method matches messages at the start of the route.

The Done/Failed/Complete/Received terminology is used in other condition methods, here checking whether an exact number of messages have been processed:

whenExactlyComplete(int)
whenExactlyDone(int)
whenExactlyFailed(int)

You can also check whether a particular message flowing through the route was successful. The index refers to the sequence of the message assigned as it is processed from the starting endpoint:

whenDoneByIndex(int)

There's more...

The NotifyBuilder can be set up to test more complex scenarios, such as checking whether certain messages reached an endpoint within a route:

new NotifyBuilder(camelContext)
  .from("activemq:in")
  .whenDone(1)
  .wereSentTo("activemq:out")
  .create();

Here, the whenDone() and wereSentTo() conditions are considered as being cumulative. You can use the Boolean and(), or(), and not() operators within the DSL to build up more complex checks. For example, here we check whether one message failed, and another succeeded:

new NotifyBuilder(camelContext)
  .from("activemq:in")
  .whenDone(1).wereSentTo("activemq:out")
  .and().whenFailed(1)
  .create();

Predicates, using Camel Expression Languages such as Simple and XPath, can also be used in your expressions:

whenAllDoneMatches(predicate)
whenAnyDoneMatches(predicate)
whenAllReceivedMatches(predicate)
whenAnyReceivedMatches(predicate)

You can use the filter() method to perform checks against certain messages only:

new NotifyBuilder(camelContext)
  .from("activemq:in")
  .whenExactlyDone(1).filter().simple("${body} contains 'test'")
  .create();

It is also possible to use the NotifyBuilder in conjunction with the MockEndpoint DSL to get a finer granularity of assertions over the message content:

MockEndpoint mock = camelContext.getEndpoint(
    "mock:nameDoesNotMatter", MockEndpoint.class);
mock.message(0).inMessage().contains(messageText);
mock.message(0).header("count").isNull();

NotifyBuilder notify = new NotifyBuilder(camelContext)
  .from("activemq:in")
  .whenDone(1).wereSentTo("activemq:out")
  .whenDoneSatisfied(mock)
  .create();

Here, the expectations set on the mock endpoint will be tested against the exchange state at the end of the route's processing.

Note

Note that the name of mock endpoint interface does not correspond to an actual endpoint within the route. It is simply used to get a handle on the DSL for the NotifyBuilder.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.124.145