Testing routes defined in Java

This recipe will introduce you to the main parts of Camel's test support by showing you how to unit test a route defined within a RouteBuilder implementation. You will do this without relying on external systems to verify the interactions.

You will see how:

  • The Camel framework gets set up and torn down
  • Mock endpoints can be used to verify the message flow, through an expect-run-verify cycle, which should be familiar to those who have worked with mocking frameworks such as EasyMock (http://www.easymock.org) in the past
  • Messages can be sent to endpoints from outside of Camel, allowing us to trigger routes with a range of payloads in order to verify the edge cases

Getting ready

To use Camel's core test support, you need to add a dependency for the camel-test library, which provides the support classes for JUnit testing as well as a transitive dependency on JUnit itself.

Add the following code to the dependencies section of your Maven POM:

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

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

How to do it...

Let us test a simple route defined as follows:

public class SimpleTransformRouteBuilder extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    from("direct:in")
      .transform(simple("Modified: ${body}"))
      .to("mock:out");
  }
}

The route consumes messages from an in-memory direct: endpoint, prepends the message body with the string Modified: and sends the result to a mock: endpoint.

To test this class, perform the following steps:

  1. Create a test class that extends org.apache.camel.test.junit4.CamelTestSupport:
    public class SimpleTransformRouteBuilderTest 
        extends CamelTestSupport {
      //...
    }

    The CamelTestSupport class is an abstract class that is responsible for instantiating the Camel context for the routes under test, creating utility objects, and injecting annotated properties into the test.

  2. Override the createRouteBuilder() method, and instantiate your RouteBuilder implementation to be tested:
    @Override
    protected RouteBuilder createRouteBuilder()
        throws Exception {
      return new SimpleTransformRouteBuilder();
    }
  3. Define the body of the test. Here, the MockEndpoint testing DSL is used to outline the messages that you expect an endpoint to receive. A test message is sent into the route via a ProducerTemplate instance provided for you by the CamelTestSupport base class. Finally, you assert that the expectations set on the mock endpoints in the Camel context were satisfied:
    @Test
    public void testPayloadIsTransformed() 
        throws InterruptedException {
      MockEndpoint mockOut = getMockEndpoint("mock:out");
      mockOut.setExpectedMessageCount(1);
      mockOut.message(0).body().isEqualTo("Modified: Cheese");
    
      template.sendBody("direct:in", "Cheese");
    
      assertMockEndpointsSatisfied();
    }

    The assertMockEndpointsSatisfied() method is a catch-all helper method that checks that all mock endpoints within your route(s) are satisfied. You can verify that the expectations of individual mock endpoints have been met with mockOut.assertIsSatisfied(), where mockOut is replaced with a variable referencing your mock endpoint.

    Note

    If an assertion on a MockEndpoint fails, it will throw a java.lang.AssertionError in your test code when you check to see if it is satisfied.

    As an alternative to explicitly fetching mocks, and referring to endpoints in each unit test, you can request an autoinjected MockEndpoint and ProducerTemplate by defining them as bean properties, and annotating them as follows:

    @EndpointInject(uri = "mock:out")
    private MockEndpoint mockOut;
    
    @Produce(uri = "direct:in")
    private ProducerTemplate in;

How it works...

The CamelTestSupport class is a convenience base class that prepares a Camel environment for your JUnit tests, without requiring that you repeatedly perform a number of repetitive steps every time you want to test a route.

As you can see from the preceding example, all that you need to do in order to set up a test is to override a base method createRouteBuilder() , and specify which properties you would like injected. Fundamentally, the base support class will perform the following:

  • Start a Camel context before each test, adding any routes that you have specified through createRouteBuilder(), or createRouteBuilders() when testing multiple RouteBuilder implementations as part of a single test
  • Inject any properties that you have annotated with @Produce and @EndpointInject
  • Stop the Camel context after each test

If you want to reproduce the preceding behavior from first principles, that is without the support class, you first define a CamelContext variable as a private member of your test:

private CamelContext camelContext;

Before each test, instantiate the context, and initialize it with the RouteBuilder class under test:

@Before
public void setUpContext() throws Exception {
  this.camelContext = new DefaultCamelContext();
  camelContext.addRoutes(new SimpleTransformRouteBuilder());
  camelContext.start();
}

After each test method, shut down the Camel context:

@After
public void cleanUpContext() throws Exception {
  camelContext.stop();
}

You then need to access the Camel context directly, through your private variable, in order to obtain handles on mock endpoints and producer template:

MockEndpoint out = 
    camelContext.getEndpoint("mock:out", MockEndpoint.class);
ProducerTemplate producerTemplate = 
    camelContext.createProducerTemplate();

This first principles approach is useful when you are testing from a framework other than JUnit or TestNG (both of which are supported by Camel), or if for some reason you need to extend a base class that itself does not extend CamelTestSupport. This approach is not used that often, as extending Camel's test support classes has proven easier and has become a best practice.

There's more...

Testing routes that consume from a direct: endpoint and produce messages to mock: endpoints is easy. In real life though, your routes will be consuming from, and producing to, endpoint technologies such as CXF for SOAP services (see Chapter 12, Web Services), and ActiveMQ for messaging. How then do you go about testing that type of route?

The Reusing routing logic through template routes recipe in Chapter 1, Structuring Routes describes a technique for externalizing endpoints. This allows you to inject real endpoints when your route is deployed, and use direct: and mock: for testing when you instantiate your RouteBuilder in createRouteBuilder() method.

To do this, the previous RouteBuilder implementation should be modified as follows:

public class SimpleTransformDIRouteBuilder extends RouteBuilder {
  private String sourceUri;
  private String targetUri;

  // setters omitted

  @Override
  public void configure() throws Exception {
    from(sourceUri)
      .transform(simple("Modified: ${body}"))
      .to(targetUri);
  }
}

Then, all that is required to test the route is to inject the direct: and mock: endpoints inside the test class:

@Override
protected RouteBuilder createRouteBuilder() throws Exception {
  SimpleTransformDIRouteBuilder routeBuilder = 
    new SimpleTransformDIRouteBuilder();
  routeBuilder.setSourceUri("direct:in");
  routeBuilder.setTargetUri("mock:out");
  return routeBuilder;
}

Note

Be careful when substituting endpoint technologies in this manner. Components may send in object types within exchanges that are different from those that you expected when writing your route. The unit testing of integrations should always be complemented with some integration testing using actual backend systems and their corresponding Camel Components.

There are further base methods within CamelTestSupport that you can override to define the details of how the Camel context will be set up. These are all prefixed with create..().

You can override the createCamelContext() method to set up the Camel context in such a way that you can test with components that are not embedded in Camel's core library (activemq:, cxf:, twitter:, leveldb:, and so on). The following example configures the ActiveMQ Component for use in your tests:

@Override
public CamelContext createCamelContext() {
  CamelContext context = new DefaultCamelContext();

  ActiveMQComponent activeMQComponent = new ActiveMQComponent();
  activeMQComponent.setBrokerURL("vm:embeddedBroker");

  context.addComponent("activemq", activeMQComponent);

  return context;
}
..................Content has been hidden....................

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