使用带有OnException定义的adviceWith进行Camel路由测试

时间:2014-05-19 12:03:02

标签: java unit-testing apache-camel

我有一个非常简单的Camel路由定义,它只包含一些OnException谓词来处理各自的异常和一些日志语句。

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");

bean本身也很简单,只是检查一个header参数并抛出一个适当的异常

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");
    }
}

由于这个测试设置只是一个较大项目的一小部分,这样做可能听起来很愚蠢,但核心意图是在测试时修改redeliveryDelay,因为“强制”IOException不需要等待3分钟,因此,为了加快单位测试,重新传递延迟可以减少到10毫秒。

为了实现这一点,我的测试方法执行以下操作:

@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();
        ...
    }
}

此处测试只是替换IOException的onException段,首先将重新传递延迟从3分钟减少到10毫秒,并在结尾处添加一个模拟端点。但是,当我尝试运行单元测试时,我将得到以下异常:

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.

然而,就我所理解的而言,official documentation中的例子非常相似。我还尝试通过定义的ID谓词及其相应的方法weaveById()或通过weaveByToString()方法查找异常定义,但没有其他结果。我还尝试通过weaveByType(OnExceptionDefinition.class).selectIndex(1).remove();删除异常定义,并通过weaveAddFirst().onException(...).async...;添加OnException部分,但结果相同。

然而,可以通过f.e.附加模拟的错误端点。 weaveByToString("Log[io exception noticed]").after().to("mock:ioError");

因此,非常欢迎任何修改onException块或redeliveryDelay以进行单元测试的提示。


@Edit:我现在也尝试按照异常消息的建议将onException声明移到路由定义(from(...))之上,这也是Camel exception samples中的首选案例。但是,在执行此操作时,所有测试(甚至是工作测试)都会因NullPointerException上的context.getRouteDefinition("someQueueID").adviceWith(context, new AdviceWithRouteBuilder() {... });而失败,因为显然无法找到路径本身。我怀疑这是一个IntelliJ问题,因为这两个类都在同一个项目中,因此对于测试类应该可以看到路径的修改。

正在使用的Camel版本:2.13.0,IntelliJ IDEA 13.1.2


@ Edit2:由于某种原因,context.getRouteDefinitions("someQueueID")如果OnException元素在from块之外定义,则返回null,而一般路由可以通过context.getRouteDefinitions().get(0)获得 - 但是,异常声明OnException部分需要作为顶级元素添加。

1 个答案:

答案 0 :(得分:6)

使用Java DSL时,路由的ID是使用.routeId()方法设置的,而不是上面编码的.id()。这可能有助于解决您的adviceWith问题。

更好的方法是使用属性来配置延迟,而不是对重试延迟进行硬编码。查看useOverridePropertiesWithPropertiesComponent()课程中CamelSpringTestSupport方法的文档。

修改

您不必编织onException子句,只需说明一个新子句即可。这是一个完整的例子:

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;
    }
}