Как дождаться завершения аннотированного метода @JMSListener в JUnit

Поэтому я пытаюсь провести интеграционное тестирование обработки JMS, кода на основе Spring (v4.1.6).

Это очень стандартная установка 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 аннотированный метод для завершения. Как мне это сделать?

3 ответа

Решение

Что ж, я надеялся, что смогу каким-то образом подключиться к жизненному циклу Message Driven Pojos, но, пройдя другие SO вопросы об асинхронном коде, я нашел решение, основанное на CountDownLatch

  1. @JMSListener аннотированный метод должен вызывать countDown() на CountDownLatch после того, как вся работа завершена:

    @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. В тестовом методе

    @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 аннотированный метод специально для интеграционного теста

Если бы вы добавили запись в аннотированный метод @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.");
}

Чтобы избежать необходимости изменять ваш фактический метод @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 Listener и объект @Mock Facade, для которого я хочу убедиться, что был вызван определенный метод.

С АОП:

  • Тестовая тема:
    • [JmsListenerTest] 2279812 - прослушиватель класса $$ EnhancerBySpringCGLIB $$ 6587f46b (упакован Spring AOP)
    • [JmsListenerTest] 30960534 - класс Facade $$ EnhancerByMockitoWithCGLIB $$ 69fe8952 (завернутый в Mockito)
  • Поток слушателя:
    • [Listener] 1151375 - класс Listener (целевой экземпляр обернутого класса AOP)
    • [Listener] 4007155 - класс FacadeImpl (не фактический экземпляр, который мы ожидали)

Без АОП:

  • Тестовая тема:
    • [JmsListenerTest] 10692528 - Слушатель класса (фактический экземпляр)
    • [JmsListenerTest] 823767 - класс Facade $$ EnhancerByMockitoWithCGLIB $$ 773538e8 (завернутый в Mockito)
  • Поток слушателя:
    • [Listener] 10692528 - класс Listener (все еще актуальный экземпляр)
    • [Listener] 823767 - класс Facade $$ EnhancerByMockitoWithCGLIB $$ 773538e8 (все еще наш поддельный экземпляр)

Это говорит о том, что вам нужно следить за использованием AOP так, как я пытался, поскольку у вас могут быть разные экземпляры в обоих потоках...

Я использую пружинные профили и использую другой 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);
   }

}
Другие вопросы по тегам