Camel route-testing using adviceWith with OnException definitions

apache-camel, java, unit-testing

Solution

When using the Java DSL, the id of the route is set using the `.routeId()` method, not `.id()` as you have coded above. That may help with your `adviceWith` concerns.

Instead of hard-coding the retry delay, a better approach would be to make the delay configurable using properties. Check out the documentation around the method `useOverridePropertiesWithPropertiesComponent()` on your `CamelSpringTestSupport` class.

EDIT

You don't have to weave the `onException` clause, simply state a new one. Here's a complete example:

import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit4.CamelTestSupport;

public class DummyTest extends CamelTestSupport{
    @Override
    protected RouteBuilder createRouteBuilder() throws Exception {
        return new RouteBuilder(){
            @Override
            public void configure() throws Exception {

                from("direct://start")
                    .routeId("myroute")
                    .onException(Exception.class)
                        .id("myException")
                        .continued(true)
                    .end()
                    .throwException(new Exception())
                    .to("mock:end");
            }
        };
    }

    @org.junit.Test
    public void doTest() throws Exception{
        context.getRouteDefinition("myroute").adviceWith(context, new AdviceWithRouteBuilder(){
            @Override
            public void configure() throws Exception {
                context.getRouteDefinition("myroute")
                    .onException(Exception.class).setBody(constant("adviceWith")).continued(true);
            }});
        context.start();
        template.sendBody("direct://start", "original");
        String bodyAtEndOfExchange = getMockEndpoint("mock:end")
                .getExchanges().get(0).getIn().getBody(String.class);
        assertEquals("adviceWith", bodyAtEndOfExchange);        
        context.stop();
    }

    @Override
    public boolean isUseAdviceWith() {
        return true;
    }
}

Problem

I have a very simple Camel route definition which just includes some OnException predicates to handle respective exceptions and some log-statements. ``` from("hazelcast:seda:someQueue") .id("someQueueID") .onException(CustomException.class) .handled(true) .log(LoggingLevel.WARN, "custom exception noticed") .end() .onException(IOException.class, FileNotFoundException.class) .asyncDelayedRedelivery() .redeliveryDelay(3*1000*60) // 3 Minutes .maximumRedeliveries(3) .log(LoggingLevel.WARN, "io exception noticed") .end() .onException(Exception.class) .log(LoggingLevel.WARN, "general exception noticed") .end() .log("Starting route") .bean(TestBean.class) .log("Finished route"); ``` The bean itself is simple too, it just checks a header parameter and throws an appropriate exception ``` public class TestBean { @Handler public void checkData(@Headers final Map<String, Object> headers) throws CustomException, IOException, Exception { Integer testVal = (Integer)headers.get("TestValue"); if (0 == testVal) throw new CustomException("CustomException"); else if (1 == testVal) throw new IOException("IOException"); else throw new Exception("Exception"); } } ``` As this test-setup is just a small part of a larger project it may sound silly to do it like presented here, but the core intend is to modify the redeliveryDelay at test time as a "forced" IOException will not need to wait 3 minutes and therefore, to speed up unit tests a bit, the redelivery delay could be reduced to like 10 ms. In order to achieve this my test-method does the following: ``` @ContextConfiguration(classes = OnExceptionRouteTest.ContextConfig.class, loader = AnnotationConfigContextLoader.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class OnExceptionRouteTest extends CamelSpringTestSupport { @Override protected AbstractApplicationContext createApplicationContext() { return new AnnotationConfigApplicationContext(ContextConfig.class) } @Configuration public static class ContextConfig extends CamelConfiguration { @Override protected void setupCamelContext(CamelContext camelContext) throws Exception { super.setupCamelContext(camelContext); camelContext.addComponent("hazelcast", new StubComponent()); // some other unnecessary stuff } @Override public List<RouteBuilder> routes() { final List<RouteBuilder> list = new ArrayList<>(); list.add(new OnExceptionRoute()); return list; } } @Override public boolean isUseAdviceWith() { return true; } @Test public void testIOException() { context.getRouteDefinition("someQueueID") .adviceWith(context, new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { this.weaveByType(OnExceptionDefinition.class) .selectIndex(1) .replace() .onException(IOException.class, FileNotFound.class) .asyncDelayedRedelivery() .redeliveryDelay(10) .maximumRedeliveries(3) .log("modified io exception noticed") .to("mock:ioError") .end(); ... mockEndpoints(); } }); context.start(); MockEndpoint ioErrorEndpoint = getMockEndpoint("mock:ioError"); ... ioErrorEndpoint.setExpectedMessageCount(1); ... Map<String, Object> headers = new HashMap<>(); headers.put("TestValue", new Integer(1)); template.sendBodyAndHeaders("hazelcast:seda:someQueue", new Object(), headers); ... ioErrorEndpoint.assertIsSatisfied(); ... } } ``` Here the test just replaces the onException segment of the IOException to first reduce the redelivery delay from 3 minutes to 10 ms and adds a mock endpoint at the end. However when I try to run the unit test I'll get the following exception: ``` java.lang.IllegalArgumentException: The output must be added as top-level on the route. Try moving OnException[[class java.io.IOException, class java.io.FileNotFoundException] -> []] to the top of route. ``` However, the examples in the official documentation, as far as I understood them correctly, are very similar. I also tried to lookup the exception definition via a defined ID predicate and its corresponding method `weaveById()` or via the `weaveByToString()` method but with no other result. I also tried to remove the exception definition via `weaveByType(OnExceptionDefinition.class).selectIndex(1).remove();` and add the OnException part via `weaveAddFirst().onException(...).async...;` but with the same result. Appending a mocked error endpoint, however, is possible via f.e. `weaveByToString("Log[io exception noticed]").after().to("mock:ioError");` So any tips for modifying onException blocks or the redeliveryDelay for unit tests are more than welcome. @Edit: I also tried now to move the onException declarations above the route definition (`from(...)`) as suggested by the exception message and this was also the preferred case in Camel's exception samples. On doing this, however, all tests (even the working ones) fail as of a `NullPointerException` on `context.getRouteDefinition("someQueueID").adviceWith(context, new AdviceWithRouteBuilder() {... });` as obviously the route itself can't be found anymore. I doubt that this is an IntelliJ issue as both classes are within the same project and therefore a modification of the route should be visible to the test-class. Camel version in use: 2.13.0, IntelliJ IDEA 13.1.2 @Edit2: For some reason `context.getRouteDefinitions("someQueueID")` returns null if the OnException elements are defined outside of the `from` block, whereas the general route can be obtained via `context.getRouteDefinitions().get(0)` - though, the exception stating that the OnException part needs to be added as top-level element remains.

Original source