简体   繁体   English

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

[英]How to wait for a spring jms listener thread to finish executing in Junit test

I have a spring boot application that uses spring-JMS.我有一个使用 spring-JMS 的 spring 启动应用程序。 Is there any way to tell the test method to wait the jms lister util it finishes executing without using latches in the actual code that will be tested?有什么方法可以告诉测试方法等待 jms lister util 它完成执行而不在将要测试的实际代码中使用锁存器?

Here is the JMS listener code:这是 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;
    }

}

Note: Is it applicable to solve this without adding testing purpose code to the implementation code (MyListener and MyProcessor).注意:是否可以在不向实现代码(MyListener 和 MyProcessor)中添加测试目的代码的情况下解决此问题。

Proxy the listener and add an advice to count down a latch;代理侦听器并添加建议以倒计时闩锁; here's one I did for a KafkaListener recently...这是我最近为 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;
    }

}

(but you should move the countDown to after the invocation proceed() ). (但您应该在调用之后将 countDown 移动到 continue proceed() )。

A method annotated with @JmsListener deletes the message after it finishes, so a good option is to read the queue for existing messages and assume the queue is empty after your method is done.用@JmsListener 注释的方法会在消息完成后删除消息,因此一个不错的选择是读取现有消息的队列,并在您的方法完成后假设队列为空。 Here is the piece of code for counting the messages from the queue.这是用于计算队列中消息的代码。

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();
      }
    });
  }

Following is the code for testing the countMessages() method.以下是测试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

I've based my solution on the answer given by Gary Russell, but rather put the CountDownLatch in an Aspect, using Spring AOP (or the spring-boot-starter-aop variant).我的解决方案基于 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());
    }

}

A snippet of the test:测试片段:

    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();

The listenerSpy is a @SpyBean annotated field of the type of my MessageListener. listenerSpy 是我的 MessageListener 类型的 @SpyBean 注释字段。 The messageCaptor is a field of type ArgumentCaptor<MyMessageType> annotated with @Captor. messageCaptor 是一个类型为ArgumentCaptor<MyMessageType>的字段,带有 @Captor 注释。 Both of these are coming from mockito so you need to run/extend your test with both MockitoExtension (or -Runner) along with the SpringExtension (or -Runner).这两个都来自mockito,因此您需要使用MockitoExtension(或-Runner)和SpringExtension(或-Runner)运行/扩展您的测试。

My code puts an object on an outbound queue after processing the incoming message, hence the putResponseOnTargetQueueAlias method.我的代码在处理传入消息后将一个对象放在出站队列中,因此使用 putResponseOnTargetQueueAlias 方法。 The captor is to intercept that object and do my assertions accordingly.捕获者将拦截该对象并相应地做出我的断言。 The same strategy could be applied to capture some other object in your logic.可以应用相同的策略来捕获逻辑中的其他对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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