所以我试图对基于Spring(v4.1.6)的JMS处理代码进行一些集成测试。
这是一个非常标准的Spring设置,带有@JmsListener
带注释的方法和DefaultMessageListenerContainer
,concurrency
设置为1
,因此只允许1个监听线程。
现在,我利用ActiveMQ的嵌入式代理不依赖任何外部jms代理来随时随地运行测试(我应该在市场营销中工作)。
所以这一切都很好,然后我有我的JUnit测试:
@Test
public void test() {
sendSomeMessage();
//how to wait here for the @JMSListener method to complete
verify();
}
我发送消息,但是我需要以某种方式等待@JMSListener
带注释的方法完成。我该怎么做?
答案 0 :(得分:1)
好吧,我希望我能以某种方式进入Message Driven Pojos生命周期来实现这一目标,但是通过其他有关异步代码的SO问题,我想出了一个基于CountDownLatch
的解决方案
在完成所有工作后,@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();
}
}
在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();
}
此解决方案的缺点是您必须实际更改专门用于集成测试的@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:
没有AOP:
这表明你需要像我尝试的那样注意使用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);
}
}