Junit 如何在ApacheCamel中对生产路线进行单元测试?

Junit 如何在ApacheCamel中对生产路线进行单元测试?,junit,mocking,routes,integration,apache-camel,Junit,Mocking,Routes,Integration,Apache Camel,假设我在单独的RouteBuilder类中创建了路由。它看起来像: 从JMS队列抓取消息 进行一些转换、验证等 根据验证结果转发到特定的JMS队列,并在数据库中保存一些内容 我想在没有JMS代理和DB的情况下对这个路由进行单元测试。我知道我可以模仿我的处理器实现,但这还不够。我不想改变这个路由(假设我在jar文件中得到了这个类)。据我从Camel in Action(第6.2.6节)了解,为了能够使用端点模拟和其他东西,我需要更改我的路由端点定义(在本书的示例中,这是“mina:tcp://

假设我在单独的RouteBuilder类中创建了路由。它看起来像:

  • 从JMS队列抓取消息
  • 进行一些转换、验证等
  • 根据验证结果转发到特定的JMS队列,并在数据库中保存一些内容
我想在没有JMS代理和DB的情况下对这个路由进行单元测试。我知道我可以模仿我的处理器实现,但这还不够。我不想改变这个路由(假设我在jar文件中得到了这个类)。据我从Camel in Action(第6.2.6节)了解,为了能够使用端点模拟和其他东西,我需要更改我的路由端点定义(在本书的示例中,这是“mina:tcp://miranda“模仿:米兰达”等)

是否可以在不改变路线定义的情况下完全隔离测试流量? 如果我将RouteBuilder作为一个单独的类来使用,我是否被迫以某种方式“复制”路由定义并手动更改它?这不是在测试错误的东西吗


我对Camel很陌生,对我来说,在开发路由时能够进行独立的单元测试真的很酷。只是为了能够改变一些东西,运行小测试,观察结果等等。

假设RouteBuilder类有硬编码的端点,那么测试就有点困难了。但是,如果RouteBuilder为端点URI使用属性占位符,那么您通常可以为单元测试使用不同的端点URI集。如骆驼书第6章所述

如果它们是硬编码的,那么您可以在单元测试中使用advice with功能,如下所示:

在Camel 2.7中,我们可以更轻松地操纵路线,因此您可以移除零件、更换零件等。这就是link所讨论的编织内容

例如,要模拟将消息发送到数据库端点,可以使用上面的命令,并将to替换为另一个命令,将其发送到模拟

在以前的版本中,您可以使用interceptSendToEndpoint技巧,这在Camel手册(第6.3.3节)中也有介绍

哦,您也可以用模拟组件替换组件,如第169页所示。现在在Camel 2.8以后的版本中,mock组件将不再抱怨它不知道的uri参数。这意味着在每个组件级别上用mock替换组件要容易得多。

   <bean id="properties" class="org.apache.camel.component.properties.PropertiesComponent">
        <property name="location" value="classpath:shop.properties"/>
    </bean>

    <route>
        <from uri="direct://stock"/>
        <to uri="{{stock.out}}"/>
    </route>

在我的spring文件中,然后在测试类路径上的shop.properties中,我有一个stock.out=xxxx,它在运行时被替换,因此我可以选择不同的路由,一个用于运行时,另一个用于测试


在6.1.6多环境单元测试中有一个更好的例子

,您可以根据Claus Ibsen的建议使用截取和建议来交换端点 回答,我认为允许您的路由接受
端点
实例,这样您的测试就不会耦合到生产端点URI

例如,假设您有一个类似于

public class MyRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("http://someapi/someresource")
        .process(exchange -> {
            // Do stuff with exchange
        })
        .to("activemq:somequeue");
    }
}
您可以这样注入端点:

public class MyRoute extends RouteBuilder {
    private Endpoint in;
    private Endpoint out;

    // This is the constructor your production code can call
    public MyRoute(CamelContext context) {
        this.in = context.getEndpoint("http://someapi/someresource");
        this.out = context.getEndpoint("activemq:somequeue");
    }

    // This is the constructor your test can call, although it would be fine
    // to use in production too
    public MyRoute(Endpoint in, Endpoint out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public void configure() throws Exception {
        from(this.in)
        .process(exchange -> {
            // Do stuff with exchange
        })
        .to(this.out);
    }
}
然后可以像这样进行测试:

public class MyRouteTest {
    private Endpoint in;
    private MockEndpoint out;
    private ProducerTemplate producer;

    @Before
    public void setup() {
        CamelContext context = new DefaultCamelContext();

        this.in = context.getEndpoint("direct:in");
        this.out = context.getEndpoint("mock:direct:out", MockEndpoint.class);
        this.producer = context.createProducerTemplate();
        this.producer.setDefaultEndpoint(this.in);

        RouteBuilder myRoute = new MyRoute(this.in, this.out);
        context.addRoutes(myRoute);

        context.start();
    }

    @Test
    public void test() throws Exception {
        this.producer.sendBody("Hello, world!");
        this.out.expectedMessageCount(1);
        this.out.assertIsSatisfied();
    }
} 
这有以下优点:

  • 您的测试非常简单,易于理解,甚至不需要扩展
    CamelTestSupport
    或其他助手类
  • CamelContext
    是手工创建的,因此您可以确保只创建了测试中的路由
  • 测试不关心生产路线URI
  • 如果需要,您仍然可以方便地将端点URI硬编码到route类中
  • 如果您使用的是Spring(这是一个好主意),我希望 分享我的方法

    您的生产路线是一个特殊类别的SpringBean 我的路线

    因此,在测试中,您可以像这样轻松地覆盖它(这是一个 spring java配置内部(到测试类)类:

    请注意,super.configure()安装您的生产路线,您可以 使用interceptFrom、interceptSendToEndpoint注入测试代码:例如。 提出一个例外

    我还添加了一些辅助路线。通过这个路径,我可以测试一个文件 已在输出文件夹中生成,它可能是JMS使用者

        @Bean
        public RouteBuilder createOutputRoute() {
    
            return new RouteBuilder() {
                @Override
                public void configure() {
    
                    fromF(FILE_IN,
                            outputDir)
                            .to("mock:output")
                            .routeId("doneRoute");
            };
    
  • 对于JMS/JDBC/。。。还有Mockrunner。通过测试配置中的以下代码,您几乎完成了:您的JMS连接工厂被模拟实现所取代,因此现在您甚至可以将某些内容放入JMS并从JMS中读取(使用上面解释的简单驼峰路由)以进行验证。别忘了在mock上创建一个队列

    @Bean(JMS\u MOCK\u CONNECTION\u工厂) @初级的 公共连接工厂jmsConnectionFactory(){ 返回(新的JMSMockObjectFactory()).getMockQueueConnectionFactory(); }

  • 我不喜欢AdviseWith,是的,它是灵活的,但它要求您在测试中手动处理camelContext,这对我来说太麻烦了。我不想在数百个测试中使用这些代码,也不想围绕它创建一个框架。此外,子类化CamelTestSupport可能是一个问题,例如,如果您要使用两个库,这两个库都要求您子类化某些内容,并且您可能有自己的测试类层次结构,而您没有看到CamelTestSupport。我尝试在测试中不使用类层次结构,以保持测试的独立性。子类化意味着,您需要一些神奇的事情发生(您不会直接在测试中看到这些代码)。如果你修改了这个魔法,你会影响很多测试。为此,我使用SpringJava配置集


  • 如果路由是xml格式的,我们如何添加测试?+1用于提及属性替换。这也是我最喜欢的技术,因为我发现它比
    adviceWith
    更快,并且在我的测试类中可读性更强(您还可以重写方法:
    useOverridePropertiesWithPropertiesComponent()
    static class TestConfig extends IntegrationTestConfig {
    
            @Bean
            public MyRoute myRoute(){
                return new MyRoute() {
                    @Override
                    public void configure() throws Exception {
                        interceptFrom(MyRoute.IN)
                                .choice() 
    
                                    .when(x -> delayThisMessagePredicate.matches(x)) //make the predicate modifiable between tests
                                    .to("log:delayed")
                                    .delay(5000)
                                .endChoice();
                        super.configure();
                    }
                };
            }
    }
    
        @Bean
        public RouteBuilder createOutputRoute() {
    
            return new RouteBuilder() {
                @Override
                public void configure() {
    
                    fromF(FILE_IN,
                            outputDir)
                            .to("mock:output")
                            .routeId("doneRoute");
            };