如何等待@JMSListener注释方法在JUnit中完成

时间:2015-07-31 10:02:51

标签: java spring asynchronous jms junit4

所以我试图对基于Spring(v4.1.6)的JMS处理代码进行一些集成测试。

这是一个非常标准的Spring设置,带有@JmsListener带注释的方法和DefaultMessageListenerContainerconcurrency设置为1,因此只允许1个监听线程。

现在,我利用ActiveMQ的嵌入式代理不依赖任何外部jms代理来随时随地运行测试(我应该在市场营销中工作)。

所以这一切都很好,然后我有我的JUnit测试:

@Test
public void test() {
    sendSomeMessage();
    //how to wait here for the @JMSListener method to complete
    verify();
}

我发送消息,但是我需要以某种方式等待@JMSListener带注释的方法完成。我该怎么做?

4 个答案:

答案 0 :(得分:1)

好吧,我希望我能以某种方式进入Message Driven Pojos生命周期来实现这一目标,但是通过其他有关异步代码的SO问题,我想出了一个基于CountDownLatch的解决方案

  1. 在完成所有工作后,@JMSListener带注释的方法应在CountDownLatch上调用countDown()

    @JmsListener(destination = "dest", containerFactory = "cf")
    public void processMessage(TextMessage message) throws JMSException {
        //do the actual processing
        actualProcessing(message);
        //if there's countDownLatch call the countdown.
        if(countDownLatch != null) {
            countDownLatch.countDown();
        }
    }
    
  2. 在testMethod

    @Test
    public void test() throws InterruptedException {
        //initialize the countDownLatch and set in on the processing class
        CountDownLatch countDownLatch = new CountDownLatch(1);
        messageProcessor.setCountDownLatch(countDownLatch);
        //sendthemessage
        sendSomeMessage();
        //wait for the processing method to call countdown()
        countDownLatch.await();
        verify();
    }
    
  3. 此解决方案的缺点是您必须实际更改专门用于集成测试的@JMSListener带注释的方法

答案 1 :(得分:0)

为了避免更改您的实际@JmsListener方法,您可以尝试在测试中使用AOP ......

首先创建一个像这样的方面类:

@Aspect
public static class JmsListenerInterceptor {
    @org.aspectj.lang.annotation.After("@annotation(org.springframework.jms.annotation.JmsListener)")
    public void afterOnMessage(JoinPoint jp) {
        // Do countdown latch stuff...
    }
}

然后将其添加到您用于测试的应用程序上下文配置中,如下所示:

<aop:aspectj-autoproxy/>
<bean id="jmsListenerInterceptor" class="path.to.your.Test$JmsListenerInterceptor" />

如果一切按计划进行,JmsListenerInterceptor将倒计时,您不必更改实际代码。

重要提示:我刚刚发现使用AOP和Mockito来验证@JmsListener中的某些方法是否被调用是一个糟糕的组合。原因似乎是额外包装到CGLib类中,导致调用错误/实际的目标实例而不是Mockito代理。

在我的测试中,我有一个@Autowired,@ InjectMocks 监听器对象和一个@Mock Facade 对象,我想验证它是否已调用某个方法。

使用AOP:

  • 测试线程:
    • [JmsListenerTest] 2279812 - 类Listener $$ EnhancerBySpringCGLIB $$ 6587f46b (由Spring AOP包装)
    • [JmsListenerTest] 30960534 - 类门面$$ EnhancerByMockitoWithCGLIB $$ 69fe8952 (由Mockito包裹)
  • 听众线程:
    • [听众] 1151375 - 类Listener (AOP包装类的目标实例)
    • [听众] 4007155 - 类FacadeImpl (不是我们预期的实际实例)

没有AOP:

  • 测试线程:
    • [JmsListenerTest] 10692528 - 类Listener (实际实例)
    • [JmsListenerTest] 823767 - 类门面$$ EnhancerByMockitoWithCGLIB $$ 773538e8 (由Mockito包裹)
  • 听众线程:
    • [听众] 10692528 - 类Listener (仍为实际实例)
    • [听众] 823767 - 课程门面$$ EnhancerByMockitoWithCGLIB $$ 773538e8 (仍然是我们的模拟实例)

这表明你需要像我尝试的那样注意使用AOP,因为你可能在两个线程中都有不同的实例......

答案 2 :(得分:0)

如果要将日志记录添加到@JmsListener注释方法中,则可以在测试类中执行类似的操作

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void test() {
    sendSomeMessage();
    //how to wait here for the @JMSListener method to complete
    Assertions.assertThat(outputCapture.toString()).contains("Message received.");
}

答案 3 :(得分:0)

我使用弹簧轮廓,并且在测试和生产代码中使用不同的Processor。在我的测试代码中,我在处理后写入BlockingQueue,可以在测试中等待

例如:

@Configuration
public class MyConfiguration {
   @Bean @Profile("!test")
   public Processor productionProcessor() {
      return new ProductionProcessor();
   }
   @Bean @Profile("test")
   public Processor testProcessor() {
      return new TestProcessor();
   }
   @Bean
   public MyListener myListener(Processor processor) {
      return new MyListener(processor);
   }
}
public class MyListener {
   private final Processor processor;
   // constructor
   @JmsListener(destination = "dest", containerFactory = "cf")
   public void processMessage(TextMessage message) throws JMSException {
      processor.process(message);
   }
}
public class TestProcessor extends ProductionProcessor {
   private final BlockingQueue<TextMessage> queue = new LinkedBlockingQueue<>();
   public void process(Textmessage message) {
      super.process(message);
      queue.add(message);
   }
   public BlockingQueue getQueue() { return queue; }
}
@SpringBootTest
@ActiveProfiles("test")
public class MyListenerTest {
   @Autowired
   private TestProcessor processor;

   @Test
   public void test() {
      sendTestMessageOverMq();
      TextMessage processedMessage = processor.getQueue().poll(10, TimeUnit.SECONDS);
      assertAllOk(processedMessage);
   }

}