Java 如何在骆驼测试中模拟AMQP消费者?

Java 如何在骆驼测试中模拟AMQP消费者?,java,testing,mocking,apache-camel,Java,Testing,Mocking,Apache Camel,假设我有以下路线: from(rabbitMQUri) .to(myCustomerProcessor) .choice() .when(shouldGotoA) .to(fizz) .when(shouldGotoB) .to(buzz) .otherwise() .to(foo); 让我们假设myCustomProcessor根据从RabbitMQ消耗的消

假设我有以下路线:

from(rabbitMQUri)
    .to(myCustomerProcessor)
    .choice()
        .when(shouldGotoA)
            .to(fizz)
        .when(shouldGotoB)
            .to(buzz)
        .otherwise()
            .to(foo);
让我们假设
myCustomProcessor
根据从RabbitMQ消耗的消息来调谐
shouldGotoA
shouldGotoB

我想对3个场景进行单元测试:

  • 一条“嘶嘶”消息被使用,并且
    shouldGotoA
    被设置为true,它在(…)时执行第一条
  • “buzz”消息被使用,并且
    shouldGotoB
    被设置为true,当(…)
    时执行第二个
  • 将使用“foo”消息并执行
    否则()
  • 我的问题是:如何模拟/存根RabbitMQ端点,以便路由在生产中正常执行,但不必实际将测试连接到RabbitMQ服务器?我需要某种“模拟消息”制作人


    一个代码示例或代码片段将非常有用,非常感谢

    这可能取决于您使用的通信组件(AMQP或RabbitMQ)

    Camel中示例代码最重要的资源是源代码中的junit测试用例

    这两个文件的功能与您所需要的类似,但您可能希望在测试用例中寻找灵感:

    使路由可测试的更“基本”的方法是将“from”uri作为参数。 假设您将RouteBuilder制作为如下内容:

       private String fromURI = "amqp:/..";
    
       public void setFromURI(String fromURI){
         this.fromURI = fromURI;
       }
    
       public void configure(){
         from(fromURI).whatever();
       }
    
    @RunWith(JukitoRunner.class)
    public class OcsFtpTest extends CamelTestSupport {
    
        public static class TestModule extends JukitoModule {
            @Override
            protected void configureTest() {
                bind(CamelContext.class).to(DefaultCamelContext.class).in(TestSingleton.class);
            }
            @Provides
            @Named("FileEndpoint")
            private Endpoint testEndpoint() {
                DirectEndpoint fileEndpoint = getContext().getEndpoint("direct:a", DirectEndpoint.class);
            return fileEndpoint;
            }
        }
        @Inject
        private MyRoutes testObject;
    
       @Test
       ....
    }
    

    然后,在开始单元测试之前,可以在fromURI中注入一个“seda:foobar”端点。终点是。这假设您不需要测试特定于AMQP/RabbitMQ的构造,只需接收有效负载。

    这是一种组合合适测试的方法

    首先定义一个空的Camel上下文,其中只有一个ProducerTemplate:

    <camel:camelContext id="camelContext">
       <camel:template id="producerTemplate" />
    </camel:camelContext>
    
    在测试本身中,将上下文中的RabbitMQ/ActiveMQ/JMS组件替换为seda或direct组件。例如,用seda队列替换所有JMS调用

    camelContext.removeComponent("jms");
    camelContext.addComponent("jms", this.camelContext.getComponent("seda"));
    camelContext.addRoutes(this.documentBatchRouting);
    
    现在,无论何时读取或写入JMS URI,它实际上都将进入seda队列。这类似于向组件中注入一个新的URI,但需要更少的配置,并允许您向路由添加新的端点,而无需担心记住注入所有URI

    最后,在测试中,使用生产者模板发送测试消息:

    producerTemplate.sendBody("jms:MyQueue", 2);
    
    然后,您需要执行该路线,您可以测试您的期望

    需要注意的两件事是:

  • 您的事务边界可能会改变,特别是当您用直接组件替换JMS队列时

  • 如果您正在测试多个路由,那么在该路由的测试结束时,必须小心地从Camel上下文中删除该路由


  • 使软件更好地可测试的一个好方法是使用依赖注入(dependency injection)(尤其是与外部组件通信的软件)。我爱它,它就是。 (所有这些东西都会给你带来学习依赖注入的负担,但我可以向你保证,很快就会有回报的)

    在这个场景中,您只需注入“端点”。您可以像这样预先配置端点(将放置在“模块”中)

    RouteBuilder只需注入端点:

    @Inject
    private MyRoutes(@Named("FileEndpoint") final Endpoint fileEndpoint) {
        this.fileEndpoint = fileEndpoint;
    }
    @Override
    public void configure() throws Exception {
        from(fileEndpoint)....
    }
    
    为了方便地测试这样的路由,您为测试注入了另一个端点,不是FileEndpoint,而是“direct:something”。一个非常简单的方法是,它将Guice与。测试如下所示:

       private String fromURI = "amqp:/..";
    
       public void setFromURI(String fromURI){
         this.fromURI = fromURI;
       }
    
       public void configure(){
         from(fromURI).whatever();
       }
    
    @RunWith(JukitoRunner.class)
    public class OcsFtpTest extends CamelTestSupport {
    
        public static class TestModule extends JukitoModule {
            @Override
            protected void configureTest() {
                bind(CamelContext.class).to(DefaultCamelContext.class).in(TestSingleton.class);
            }
            @Provides
            @Named("FileEndpoint")
            private Endpoint testEndpoint() {
                DirectEndpoint fileEndpoint = getContext().getEndpoint("direct:a", DirectEndpoint.class);
            return fileEndpoint;
            }
        }
        @Inject
        private MyRoutes testObject;
    
       @Test
       ....
    }
    

    现在,“testObject”将获得直接端点,而不是文件端点。这适用于所有类型的端点,通常适用于所有严重依赖接口的接口/抽象类和API(camel在这里很出色!)。

    谢谢@Petter(+1),但我担心有人会给我一个“你应该注入字符串URI”-键入答案。。。我需要(…)
    中模拟/存根的内容来实际生成正确路由的消息。我不相信给它注射seda:foo能帮我做到这一点……有什么想法吗?再次感谢!隐马尔可夫模型。。uri注入是我的第二个选择(有时已经足够好了)。查看来自AMQ源代码的两个链接的.java文件,以获得更合适的模拟,以便实际生成消息,只需在测试代码()中使用一个ProducerTemplate即可。还要记住,如果用SEDA或direct endpoint替换RibbitMQ,您的事务行为可能会改变。谢谢@matthelliwell(+1)-但请考虑一下:如果我添加一个
    ProducerTemplate
    ,我应该如何注入它而不是RabbitMQ端点?在我的回答中,有AMQP路由的示例测试类,以及骆驼本身链接到的RabbitMQ路由。他们会用“from”等来测试完整的路线。请阅读。谢谢@matt helliwell(+1)-我今晚回家后会检查这个,并会在这里发帖反馈!同时还有一个快速跟进的问题:当你说我的“交易边界可能会改变”时,你到底是什么意思?你能举个具体的例子吗?请记住,我使用AMQP/RabbitMQ进行实际的消息传递(在生产路线中)。再次感谢!一种情况是,您正在测试的类中进行DB更新,并且依赖测试用例回滚事务。如果您有一个seda/jms队列,那么在测试用例中创建的事务将不会传播到被测试的类,因此您不能回滚它们所做的任何DB更新。如果您使用的是直接组件,它就可以正常工作。类似地,如果在路由中用“direct:”组件替换jms队列,则会得到任何通过“direct:”组件传播的事务。什么是camelContext.addRoutes(this.documentBatchRouting);?我用rabbitmq尝试了这个方法,但组件没有被删除。添加路由后,它将尝试连接到rabbitmq而不是seda。它似乎只适用于可以接受相同参数的端点。SEDA不接受应用于rabbit的所有参数,所以在替换它时失败。我看不到模块类中可用的getContext()函数。你能帮我吗