如何在Apache Camel中对生产路由进行单元测试?

23
假设我在单独的RouteBuilder类中创建了我的路由,它看起来像:
  • 从JMS队列获取消息
  • 进行某些转换、验证等操作
  • 根据验证结果将消息转发到特定的JMS队列并保存一些东西到数据库中
我想在没有JMS代理和数据库的情况下对这条路线进行单元测试。我知道我可以模拟我的Processor实现,但这还不够。我不想改变这条路线(假设我获取了该类的Jar文件)。据我所知,在Camel in Action(第6.2.6节)中,为了能够使用端点和其他东西的模拟,我需要更改我的路由终点定义(在书中的示例中,这是将“mina:tcp://miranda”更改为“mock:miranda”等)。
是否可以在不更改路由定义的情况下完全隔离地测试流程?如果我的RouteBuilder作为单独的类存在,那么我是否被迫以某种方式“复制”路由定义并手动更改它?这不是测试错误的事情吗?
我对Camel还很陌生,因此在开发路由时能够进行隔离的单元测试将非常酷。只需更改一些内容,运行小型测试,观察结果等。
4个回答

25

假设RouteBuilder类有硬编码的端点,那么测试会有一定的困难。然而,如果RouteBuilder使用属性占位符作为端点URI,则通常可以为单元测试使用不同的端点URI集。如Camel书的第6章所述。

如果它们是硬编码的,则可以在单元测试中使用advice with特性,如下所示:https://camel.apache.org/components/latest/others/test-cdi.html#CDITesting-RoutesadvisingwithadviceWith

在Camel 2.7中,我们使得路由更易于操作,因此您可以删除部分,替换部分等。这就是链接谈到的编织部分。

例如,要模拟将消息发送到数据库端点,可以使用以上内容并将其替换为另一个将其发送到模拟器的位置。

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

哦,您还可以像第169页所示那样使用模拟组件来替换组件。现在,在Camel 2.8及以上版本中,模拟组件不再抱怨它不知道的URI参数。这意味着更容易以每个组件级别使用模拟来替换组件。


6

我有

   <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单元测试多个环境中有一个更好的例子。

+1 提到属性替换。这也是我最喜欢的技术,因为我发现它比 adviceWith 更快,并且在我的测试类中更易读(您还可以重写方法:useOverridePropertiesWithPropertiesComponent())。 - рüффп

4

虽然您可以使用拦截器和建议来根据Claus Ibsen的答案交换端点,但我认为最好允许您的路由接受Endpoint实例,这样您的测试不会与生产端点URI耦合。

例如,假设您有一个类似以下内容的RouteBuilder

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硬编码到路由类中

我们如何在XML中添加路由测试? - Saket Puranik
骆驼组件/组件/模拟(Mock) (https://camel.apache.org/components/latest/mock-component.html) 系统提示: "仅支持生产者"。那么,this.out = context.getEndpoint("mock:direct:out", MockEndpoint.class); 这行代码怎么能工作呢? - Gerold Broser

0
如果你正在使用Spring(这通常是一个好主意),我想分享一下我的方法。
你的生产路由是一个带有特殊类MyRoute的Spring bean。
@Component public class MyRoute extends RouteBuilder {
public static final String IN = "jms://inqueue";
@Override public void configure() throws Exception { from(IN) .process(exchange -> { // 处理exchange }) .to("activemq:somequeue"); } }
所以在测试中,你可以很容易地像这样覆盖它(这是一个spring java配置内部(对于测试类)的类):
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)) // 使predicate在测试之间可修改 .to("log:delayed") .delay(5000) .endChoice(); super.configure(); } }; } }
注意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读取(使用简单的camel route,就像上面解释的那样)进行验证。不要忘记在模拟中创建一个队列。
@Bean(JMS_MOCK_CONNECTION_FACTORY) @Primary public ConnectionFactory jmsConnectionFactory() { return (new JMSMockObjectFactory()).getMockQueueConnectionFactory(); }
我不喜欢AdviseWith,是的,它很灵活,但是在测试中需要手动处理camelContext,这对我来说太过于侵入性了。我不想在成百上千个测试中加入那段代码,也不想为此创建一个框架。而且,如果你使用两个库,这两个库都需要你继承某个类,并且你可能有自己的测试类层次结构,在其中看不到CamelTestSupport。我尽量避免在我的测试中有类层次结构,以保持测试的独立性。子类化意味着你需要发生一些魔法(你在测试中看不到那段代码)。如果你修改了那个魔法,你会影响很多测试。我在这方面使用spring java配置集。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接