繁体   English   中英

如何在 Junit 测试中等待 spring jms 侦听器线程完成执行

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM