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

发布于 2024-11-01 03:24:41 字数 515 浏览 1 评论 0原文

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

  • 从 JMS 队列中获取消息,
  • 进行一些转换、验证等
  • 根据验证结果转发到特定的 JMS 队列,

,并将某些内容保存在数据库中。我想在没有 JMS 代理和数据库的情况下对该路由进行单元测试。我知道我可以模拟我的处理器实现,但这还不够。我不想更改这条路线(假设我在 jar 文件中得到了该类)。据我从《Camel in Action》(第 6.2.6 节)中了解到,为了能够使用端点的模拟和其他内容,我需要更改我的路由端点定义(在本书的示例中,这是“mina:tcp:/”的更改) /米兰达”到“模拟:米兰达”等)。

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

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

Let's say I have my routes created in separate RouteBuilder class. It looks like:

  • grab message from JMS queue
  • do some transformation, validation etc
  • depending on validation results forward to specific JMS queue and save something in DB

I'd like to unit test this route with no JMS broker and no DB. I know I can mock my Processor implementations but that's not enough. I don't want to change this route (let's suppose I got that class in jar file). As far as I know from Camel in Action (sec. 6.2.6), to be able to use mocks of endpoints and other stuff I need to change my route endpoint definitions (in book's example this is change of "mina:tcp://miranda" to "mock:miranda" etc).

Is it possible to test the flow in complete isolation without changing route definitions?
If I got my RouteBuilder as a separate class, am I forced to somehow "copy" route definition and change it manually? Isn't it testing the wrong thing?

I'm quite new to Camel and for me it'd be really cool to be able to have isolated unit test while deveoping routes. Just to be able to change something, run small test, observe result and so on.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

狼性发作 2024-11-08 03:24:41

假设 RouteBuilder 类具有硬编码端点,那么测试起来会有点困难。但是,如果 RouteBuilder 使用端点 uri 的属性占位符,那么您通常可以使用一组不同的端点 uri 进行单元测试。正如 Camel 书第 6 章所解释的那样。

如果它们是硬编码的,那么您可以在单元测试中使用带有功能的建议,如下所示: https://camel.apache.org/components/latest/others/test-cdi.html#CDITesting-RoutesadvisingwithadviceWith

在 Camel 2.7 中,我们使操作路线变得更加容易,这样你就可以移除零件,更换零件等。这就是链接谈到的编织内容。

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

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

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

Assuming the RouteBuilder class has hardcoded endpoints then its a bit tougher to test. However if the RouteBuilder using the property placeholder for endpoint uris, then you often will be able to use a different set of endpoint uris for unit tests. As explained in chapter 6 of the Camel book.

If they are hardcoded then you can use the advice with feature in your unit test as shown here: https://camel.apache.org/components/latest/others/test-cdi.html#CDITesting-RoutesadvisingwithadviceWith

In Camel 2.7 we made it possible to manipulate the route much easier, so you can remove parts, replace parts, etc. Thats the weaving stuff that link talks about.

For example to simulate sending a message to a database endpoint, you can use that above and replace the to with another where you send it to a mock instead.

In previous releases you can use the interceptSendToEndpoint trick, which is also covered in the Camel book (section 6.3.3)

Oh you can also replace components with mock component as shown on page 169. Now in Camel 2.8 onwards the mock component will no longer complain about uri parameters it doesnt know. That means its much easier to replace components with mocks on a per component level.

傲鸠 2024-11-08 03:24:41

   <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 多环境下的单元测试

I have

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

in my spring file and then in the shop.properties on the test class path i have a stock.out=xxxx which is replaced at runtime so i can have to different routes one for runtime and one for test

theres a better example in 6.1.6 unit testing in multiple environments

枕头说它不想醒 2024-11-08 03:24:41

虽然您可以按照克劳斯·易卜生 (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 硬编码到路由类中

While you can use intercepts and advice to swap out endpoints as per Claus Ibsen's
answer, I think that it is far better to allow your routes to accept Endpoint
instances so that your tests aren't coupled to your production endpoint URIs.

For example, say you have a RouteBuilder that looks something like

public class MyRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("http://someapi/someresource")
        .process(exchange -> {
            // Do stuff with exchange
        })
        .to("activemq:somequeue");
    }
}

You can make it possible to inject endpoints like so:

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

Which can then be tested like this:

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

This has the following advantages:

  • your test is very simple and easy to understand, and doesn't even need to extend CamelTestSupport or other helper classes
  • the CamelContext is created by hand so you can be sure that only the route under test is created
  • the test doesn't care about the production route URIs
  • you still have the convenience of hard-coding the endpoint URIs into the route class if you want
黯淡〆 2024-11-08 03:24:41
  1. 如果您使用 Spring(这主要是一个好主意),我想
    分享我的方法。

    你的生产路线是一个具有特殊类别的 Spring bean
    我的路线

    <前><代码>@Component
    公共类 MyRoute 扩展 RouteBuilder {

    公共静态最终字符串IN =“jms://inqueue”;

    @覆盖
    公共无效配置()抛出异常{
    来自(IN)
    .process(交换->{
    // 通过交换做一些事情
    })
    .to("activemq:somequeue");
    }
    }

    所以在测试中你可以像这样轻松地覆盖它(这是一个
    spring java config内部(到测试类)类):

    静态类 TestConfig 扩展 IntegrationTestConfig {
    
            @豆
            公共我的路线我的路线(){
                返回新的 MyRoute() {
                    @覆盖
                    公共无效配置()抛出异常{
                        拦截(MyRoute.IN)
                                。选择() 
    
                                    .when(x -> delayThisMessagePredicate.matches(x)) //使谓词在测试之间可修改
                                    .to("日志:延迟")
                                    .延迟(5000)
                                .endChoice();
                        超级配置();
                    }
                };
            }
    }
    

    注意 super.configure() 安装你的生产路线,你可以
    使用interceptFrom、interceptSendToEndpoint注入测试代码:eg
    引发异常。

    我还添加了一些辅助路线。通过这条路线,我可以测试文件是否具有
    已在输出文件夹中生成,它可能是 JMS 消费者...

    <前><代码>@Bean
    公共 RouteBuilder createOutputRoute() {

    返回新的 RouteBuilder() {
    @覆盖
    公共无效配置(){

    来自F(FILE_IN,
    输出目录)
    .to("模拟:输出")
    .routeId("完成路由");
    };

  2. 对于 JMS/JDBC/... 还有 Mockrunner。使用测试配置中的以下代码,您几乎完成了:您的 JMS 连接工厂已被模拟实现替换,因此现在您甚至可以将某些内容放入 JMS 并从 jms 读取(使用如上所述的简单骆驼路由)进行验证。不要忘记在模拟上创建一个队列。

    @Bean(JMS_MOCK_CONNECTION_FACTORY)
    @基本的
    公共 ConnectionFactory jmsConnectionFactory() {
    return (new JMSMockObjectFactory()).getMockQueueConnectionFactory();
    }

  3. 我不喜欢AdviseWith,是的,它很灵活,但是它要求你在测试中手动处理camelContext,这对我来说太具有侵入性。我不想将该代码放入数百个测试中,我也不想围绕它创建一个框架。另外,子类化 CamelTestSupport 也可能是一个问题,例如,如果您使用两个库,这两个库都要求您子类化某些内容,并且您可能有自己的测试类层次结构,但您看不到 CamelTestSupport。我尽量不在测试中使用类层次结构,以使测试保持独立。子类化意味着,您需要一些魔法才能发生(您在测试中不会直接看到该代码)。如果你修改那个魔法,就会影响很多测试。我为此使用 spring java 配置集。

  1. In case you are using Spring (which is mostly a good idea), I'd like
    to share my approach.

    Your production route is a Spring bean with it's special class
    MyRoute

    @Component
    public class MyRoute extends RouteBuilder {
    
        public static final String IN = "jms://inqueue";
    
        @Override
        public void configure() throws Exception {
            from(IN)
            .process(exchange -> {
                // Do stuff with exchange
            })
            .to("activemq:somequeue");
        }
    }
    

    So in the test you can easily override it like this (this is a
    spring java config internal (to the test class) class):

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

    Notice super.configure() installs your production route and you may
    use interceptFrom, interceptSendToEndpoint to inject test code: e.g.
    raise an exception.

    I also add some helper routes. With this route I can test, that a file has
    been generated in an output folder, it may be a JMS consumer...

        @Bean
        public RouteBuilder createOutputRoute() {
    
            return new RouteBuilder() {
                @Override
                public void configure() {
    
                    fromF(FILE_IN,
                            outputDir)
                            .to("mock:output")
                            .routeId("doneRoute");
            };
    
  2. For JMS/JDBC/... there is also Mockrunner. With the code below in your test config you are almost done: your JMS Connection Factory is replaced by a mock implementation, so now you can even put something to JMS and read from jms (using simple camel route like explained above) to verify. Don't forget to create a queue on the mock.

    @Bean(JMS_MOCK_CONNECTION_FACTORY)
    @Primary
    public ConnectionFactory jmsConnectionFactory() {
    return (new JMSMockObjectFactory()).getMockQueueConnectionFactory();
    }

  3. I don't like AdviseWith, yes it's flexible, but it requires you in the test to manually handle camelContext, which is too intrusive for me. I don't want to put that code in hundreds of tests and I also don't want to create a framework around it. Also subclassing CamelTestSupport can be a problem for example if you would use two libraries, which both require you to subclass something and you might have your own test class hierarchy, where you don't see CamelTestSupport. I try not to have class hierarchy in my tests to leave the tests independent. Subclassing means, that you require some magic to happen (you don't directly see that code in the test). If you modify that magic, you would affect a lot of tests. I use spring java config sets for that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文