Testing

Testing is a crucial activity in any piece of software development or integration. Typically Camel Riders use various different technologies wired together in a variety of patterns with different expression languages together with different forms of Bean Integration and Dependency Injection so its very easy for things to go wrong! . Testing is the crucial weapon to ensure that things work as you would expect.

Camel is a Java library so you can easily wire up tests in whatever unit testing framework you use (JUnit 3.x (deprecated), 4.x, or TestNG). However the Camel project has tried to make the testing of Camel as easy and powerful as possible so we have introduced the following features.

Testing mechanisms

The following mechanisms are supported

Name Component Description
Camel Test camel-test Is a standalone Java library letting you easily create Camel test cases using a single Java class for all your configuration and routing without using Spring or Guice for Dependency Injection which does not require an in-depth knowledge of Spring + Spring Test or Guice.  Supports JUnit 3.x (deprecated) and JUnit 4.x based tests.
Spring Testing camel-test-spring Supports JUnit 3.x (deprecated) or JUnit 4.x based tests that bootstrap a test environment using Spring without needing to be familiar with Spring Test.  The  plain JUnit 3.x/4.x based tests work very similar to the test support classes in camel-test.  Also supports Spring Test based tests that use the declarative style of test configuration and injection common in Spring Test.  The Spring Test based tests provide feature parity with the plain JUnit 3.x/4.x based testing approach.  Notice camel-test-spring is a new component in Camel 2.10 onwards. For older Camel release use camel-test which has built-in Spring Testing.
Blueprint Testing camel-test-blueprint Camel 2.10: Provides the ability to do unit testing on blueprint configurations
Guice camel-guice Uses Guice to dependency inject your test classes
Camel TestNG camel-testng
Supports plain TestNG based tests with or without Spring or Guice for Dependency Injection which does not require an in-depth knowledge of Spring + Spring Test or Guice.  Also from Camel 2.10 onwards, this component supports Spring Test based tests that use the declarative style of test configuration and injection common in Spring Test and described in more detail under Spring Testing.

In all approaches the test classes look pretty much the same in that they all reuse the Camel binding and injection annotations.

Camel Test Example

Here is the Camel Test example.

public class FilterTest extends CamelTestSupport {

    @EndpointInject(uri = "mock:result")
    protected MockEndpoint resultEndpoint;

    @Produce(uri = "direct:start")
    protected ProducerTemplate template;

    @Test
    public void testSendMatchingMessage() throws Exception {
        String expectedBody = "<matched/>";

        resultEndpoint.expectedBodiesReceived(expectedBody);

        template.sendBodyAndHeader(expectedBody, "foo", "bar");

        resultEndpoint.assertIsSatisfied();
    }

    @Test
    public void testSendNotMatchingMessage() throws Exception {
        resultEndpoint.expectedMessageCount(0);

        template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");

        resultEndpoint.assertIsSatisfied();
    }

    @Override
    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
                from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
            }
        };
    }
}

Notice how it derives from the Camel helper class CamelTestSupport but has no Spring or Guice dependency injection configuration but instead overrides the createRouteBuilder() method.

Spring Test with XML Config Example

Here is the Spring Testing example using XML Config.

@ContextConfiguration
public class FilterTest extends AbstractJUnit38SpringContextTests {

    @EndpointInject(uri = "mock:result")
    protected MockEndpoint resultEndpoint;

    @Produce(uri = "direct:start")
    protected ProducerTemplate template;

    @DirtiesContext
    public void testSendMatchingMessage() throws Exception {
        String expectedBody = "<matched/>";

        resultEndpoint.expectedBodiesReceived(expectedBody);

        template.sendBodyAndHeader(expectedBody, "foo", "bar");

        resultEndpoint.assertIsSatisfied();
    }

    @DirtiesContext
    public void testSendNotMatchingMessage() throws Exception {
        resultEndpoint.expectedMessageCount(0);

        template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");

        resultEndpoint.assertIsSatisfied();
    }
}

Notice that we use @DirtiesContext on the test methods to force Spring Testing to automatically reload the CamelContext after each test method - this ensures that the tests don't clash with each other (e.g. one test method sending to an endpoint that is then reused in another test method).

Also notice the use of @ContextConfiguration to indicate that by default we should look for the FilterTest-context.xml on the classpath to configure the test case which looks like this

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

  <camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
      <from uri="direct:start"/>
      <filter>
        <xpath>$foo = 'bar'</xpath>
        <to uri="mock:result"/>
      </filter>
    </route>
  </camelContext>

</beans>

Spring Test with Java Config Example

Here is the Spring Testing example using Java Config. For more information see Spring Java Config.

@ContextConfiguration(
        locations = "org.apache.camel.spring.javaconfig.patterns.FilterTest$ContextConfig",
        loader = JavaConfigContextLoader.class)
public class FilterTest extends AbstractJUnit4SpringContextTests {

    @EndpointInject(uri = "mock:result")
    protected MockEndpoint resultEndpoint;

    @Produce(uri = "direct:start")
    protected ProducerTemplate template;

    @DirtiesContext
    @Test
    public void testSendMatchingMessage() throws Exception {
        String expectedBody = "<matched/>";

        resultEndpoint.expectedBodiesReceived(expectedBody);

        template.sendBodyAndHeader(expectedBody, "foo", "bar");

        resultEndpoint.assertIsSatisfied();
    }

    @DirtiesContext
    @Test
    public void testSendNotMatchingMessage() throws Exception {
        resultEndpoint.expectedMessageCount(0);

        template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");

        resultEndpoint.assertIsSatisfied();
    }

    @Configuration
    public static class ContextConfig extends SingleRouteCamelConfiguration {
        @Bean
        public RouteBuilder route() {
            return new RouteBuilder() {
                public void configure() {
                    from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
                }
            };
        }
    }
}

This is similar to the XML Config example above except that there is no XML file and instead the nested ContextConfig class does all of the configuration; so your entire test case is contained in a single Java class. We currently have to reference by class name this class in the @ContextConfiguration which is a bit ugly. Please vote for SJC-238 to address this and make Spring Test work more cleanly with Spring JavaConfig.

Its totally optional but for the ContextConfig implementation we derive from SingleRouteCamelConfiguration which is a helper Spring Java Config class which will configure the CamelContext for us and then register the RouteBuilder we create.

Spring Test with XML Config and Declarative Configuration Example

Here is a Camel test support enhanced Spring Testing example using XML Config and pure Spring Test based configuration of the Camel Context.

@RunWith(CamelSpringJUnit4ClassRunner.class)
@ContextConfiguration
// Put here to prevent Spring context caching across tests and test methods since some tests inherit 
// from this test and therefore use the same Spring context.  Also because we want to reset the
// Camel context and mock endpoints between test methods automatically.
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class CamelSpringJUnit4ClassRunnerPlainTest {
    
    @Autowired
    protected CamelContext camelContext;
    
    @Autowired
    protected CamelContext camelContext2;
    
    @EndpointInject(uri = "mock:a", context = "camelContext")
    protected MockEndpoint mockA;
    
    @EndpointInject(uri = "mock:b", context = "camelContext")
    protected MockEndpoint mockB;
    
    @EndpointInject(uri = "mock:c", context = "camelContext2")
    protected MockEndpoint mockC;
    
    @Produce(uri = "direct:start", context = "camelContext")
    protected ProducerTemplate start;
    
    @Produce(uri = "direct:start2", context = "camelContext2")
    protected ProducerTemplate start2;
    
    @Test
    public void testPositive() throws Exception {
        assertEquals(ServiceStatus.Started, camelContext.getStatus());
        assertEquals(ServiceStatus.Started, camelContext2.getStatus());
        
        mockA.expectedBodiesReceived("David");
        mockB.expectedBodiesReceived("Hello David");
        mockC.expectedBodiesReceived("David");
        
        start.sendBody("David");
        start2.sendBody("David");
        
        MockEndpoint.assertIsSatisfied(camelContext);
    }
    
    @Test
    public void testJmx() throws Exception {
        assertEquals(DefaultManagementStrategy.class, camelContext.getManagementStrategy().getClass());
    }
    
    @Test
    public void testShutdownTimeout() throws Exception {
        assertEquals(10, camelContext.getShutdownStrategy().getTimeout());
        assertEquals(TimeUnit.SECONDS, camelContext.getShutdownStrategy().getTimeUnit());
    }
    
    @Test
    public void testStopwatch() {
        StopWatch stopWatch = StopWatchTestExecutionListener.getStopWatch();
        
        assertNotNull(stopWatch);
        assertTrue(stopWatch.taken() < 100);
    }
    
    @Test
    public void testExcludedRoute() {
        assertNotNull(camelContext.getRoute("excludedRoute"));
    }
    
    @Test
    public void testProvidesBreakpoint() {
        assertNull(camelContext.getDebugger());
        assertNull(camelContext2.getDebugger());
    }

    @SuppressWarnings("deprecation")
    @Test
    public void testLazyLoadTypeConverters() {
        assertTrue(camelContext.isLazyLoadTypeConverters());
        assertTrue(camelContext2.isLazyLoadTypeConverters());
    }
}

Notice how a custom test runner is used with the @RunWith annotation to support the features of CamelTestSupport through annotations on the test class.  See Spring Testing for a list of annotations you can use in your tests.

Blueprint Test

Here is the Blueprint Testing example using XML Config.

// to use camel-test-blueprint, then extend the CamelBlueprintTestSupport class,
// and add your unit tests methods as shown below.
public class DebugBlueprintTest extends CamelBlueprintTestSupport {

    // override this method, and return the location of our Blueprint XML file to be used for testing
    @Override
    protected String getBlueprintDescriptor() {
        return "org/apache/camel/test/blueprint/camelContext.xml";
    }

    // here we have regular Junit @Test method
    @Test
    public void testRoute() throws Exception {
        // set mock expectations
        getMockEndpoint("mock:a").expectedMessageCount(1);

        // send a message
        template.sendBody("direct:start", "World");

        // assert mocks
        assertMockEndpointsSatisfied();
    }

}

Also notice the use of getBlueprintDescriptors to indicate that by default we should look for the camelContext.xml in the package to configure the test case which looks like this

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
             http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

  <camelContext xmlns="http://camel.apache.org/schema/blueprint">

    <route>
      <from uri="direct:start"/>
      <transform>
        <simple>Hello ${body}</simple>
      </transform>
      <to uri="mock:a"/>
    </route>

  </camelContext>

</blueprint>

Testing endpoints

Camel provides a number of endpoints which can make testing easier.

Name Description
DataSet For load & soak testing this endpoint provides a way to create huge numbers of messages for sending to Components and asserting that they are consumed correctly
Mock For testing routes and mediation rules using mocks and allowing assertions to be added to an endpoint
Test Creates a Mock endpoint which expects to receive all the message bodies that could be polled from the given underlying endpoint

The main endpoint is the Mock endpoint which allows expectations to be added to different endpoints; you can then run your tests and assert that your expectations are met at the end.

Stubbing out physical transport technologies

If you wish to test out a route but want to avoid actually using a real physical transport (for example to unit test a transformation route rather than performing a full integration test) then the following endpoints can be useful.

Name Description
Direct Direct invocation of the consumer from the producer so that single threaded (non-SEDA) in VM invocation is performed which can be useful to mock out physical transports
SEDA Delivers messages asynchonously to consumers via a java.util.concurrent.BlockingQueue which is good for testing asynchronous transports

Testing existing routes

Camel provides some features to aid during testing of existing routes where you cannot or will not use Mock etc. For example you may have a production ready route which you want to test with some 3rd party API which sends messages into this route.

Name Description
NotifyBuilder Allows you to be notified when a certain condition has occurred. For example when the route has completed 5 messages. You can build complex expressions to match your criteria when to be notified.
AdviceWith Allows you to advice or enhance an existing route using a RouteBuilder style. For example you can add interceptors to intercept sending outgoing messages to assert those messages are as expected.
© 2004-2011 The Apache Software Foundation.
Apache Camel, Camel, Apache, the Apache feather logo, and the Apache Camel project logo are trademarks of The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their respective owners.
Graphic Design By Hiram