[英]How to wait for a spring jms listener thread to finish executing in Junit test
我有一個使用 spring-JMS 的 spring 啟動應用程序。 有什么方法可以告訴測試方法等待 jms lister util 它完成執行而不在將要測試的實際代碼中使用鎖存器?
這是 JMS 偵聽器代碼:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
@Component
public class MyListener {
@Autowired
MyProcessor myProcessor;
@JmsListener(destination = "myQueue", concurrency = "1-4")
private void onMessage(Message message, QueueSession session) {
myProcessor.processMessage(message, session);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
@Component
public class MyProcessor {
public void processMessage(Message msg, QueueSession session) {
//Here I have some code.
}
}
import org.apache.activemq.command.ActiveMQTextMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueSession;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@SpringBootTest
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
public class IntegrationTest {
@Autowired
private JmsTemplate JmsTemplate;
@Test
public void myTest() throws JMSException {
Message message = new ActiveMQTextMessage();
jmsTemplate.send("myQueue", session -> message);
/*
Here I have some testing code. How can I tell the application
to not execute this testing code until all JMS lister threads
finish executing.
*/
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.SocketUtils;
import javax.jms.ConnectionFactory;
@EnableJms
@Configuration
@Profile("test")
public class JmsTestConfig {
public static final String BROKER_URL =
"tcp://localhost:" + SocketUtils.findAvailableTcpPort();
@Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.addConnector(BROKER_URL);
return brokerService;
}
@Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(BROKER_URL);
}
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
}
注意:是否可以在不向實現代碼(MyListener 和 MyProcessor)中添加測試目的代碼的情況下解決此問題。
代理偵聽器並添加建議以倒計時閂鎖; 這是我最近為 KafkaListener 所做的一個...
@Test
public void test() throws Exception {
this.template.send("so50214261", "foo");
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received.get()).isEqualTo("foo");
}
@Configuration
public static class TestConfig {
private static final AtomicReference<String> received = new AtomicReference<>();
private static final CountDownLatch latch = new CountDownLatch(1);
@Bean
public static MethodInterceptor interceptor() {
return invocation -> {
received.set((String) invocation.getArguments()[0]);
return invocation.proceed();
};
}
@Bean
public static BeanPostProcessor listenerAdvisor() {
return new ListenerWrapper(interceptor());
}
}
public static class ListenerWrapper implements BeanPostProcessor, Ordered {
private final MethodInterceptor interceptor;
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public ListenerWrapper(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Listener) {
ProxyFactory pf = new ProxyFactory(bean);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(this.interceptor);
advisor.addMethodName("listen");
pf.addAdvisor(advisor);
return pf.getProxy();
}
return bean;
}
}
(但您應該在調用之后將 countDown 移動到 continue proceed()
)。
用@JmsListener 注釋的方法會在消息完成后刪除消息,因此一個不錯的選擇是讀取現有消息的隊列,並在您的方法完成后假設隊列為空。 這是用於計算隊列中消息的代碼。
private int countMessages() {
return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {
@Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
return Collections.list(browser.getEnumeration()).size();
}
});
}
以下是測試countMessages()
方法的代碼。
jmsTemplate.convertAndSend(queueName, "***MESSAGE CONTENT***");
while (countMessages() > 0) {
log.info("number of pending messages: " + countMessages());
Thread.sleep(1_000l);
}
// continue with your logic here
我的解決方案基於 Gary Russell 給出的答案,而是使用 Spring AOP(或 spring-boot-starter-aop 變體)將 CountDownLatch 放在 Aspect 中。
public class TestJMSConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJMSConfiguration.class);
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
@Component
@Aspect
public static class LatchCounterAspect {
@Pointcut("execution(public void be.infrabel.rocstdm.application.ROCSTDMMessageListener.onMessage(javax.jms.TextMessage))")
public void onMessageMethod() {};
@After(value = "onMessageMethod()")
public void countDownLatch() {
countDownLatch.countDown();
LOGGER.info("CountDownLatch called. Count now at: {}", countDownLatch.getCount());
}
}
測試片段:
JmsTemplate jmsTemplate = new JmsTemplate(this.embeddedBrokerConnectionFactory);
jmsTemplate.convertAndSend("AQ.SOMEQUEUE.R", message);
TestJMSConfiguration.countDownLatch.await();
verify(this.listenerSpy).putResponseOnTargetQueueAlias(messageCaptor.capture());
RouteMessage outputMessage = messageCaptor.getValue();
listenerSpy 是我的 MessageListener 類型的 @SpyBean 注釋字段。 messageCaptor 是一個類型為ArgumentCaptor<MyMessageType>
的字段,帶有 @Captor 注釋。 這兩個都來自mockito,因此您需要使用MockitoExtension(或-Runner)和SpringExtension(或-Runner)運行/擴展您的測試。
我的代碼在處理傳入消息后將一個對象放在出站隊列中,因此使用 putResponseOnTargetQueueAlias 方法。 捕獲者將攔截該對象並相應地做出我的斷言。 可以應用相同的策略來捕獲邏輯中的其他對象。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.