Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Camel route-testing using adviceWith with OnException definitions

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.

like image 946
Roman Vottner Avatar asked May 19 '14 12:05

Roman Vottner


People also ask

How do you handle exceptions on a camel route?

You use this route if you need to create some custom response message back to the caller, or do any other processing because that exception was thrown. If continued is true, then Camel will catch the exception and in fact just ignore it and continue routing in the original route.

What are routes in camel?

Contents. A Camel route is where the integration flow is defined. For example to integrate two systems then a Camel route can be coded to specify how these systems are integrated. An example could be to take files from a FTP server and send to a ActiveMQ messaging system.

What is Route ID in Apache Camel?

routeId() are for identifying routes. By adding ids you can in your tests use adviceWith() to mock or inject or remove parts of your route to perform automated tests without having access to backend systems.


1 Answers

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;
    }
}
like image 130
Adam Hawkes Avatar answered Nov 15 '22 06:11

Adam Hawkes