How to Wait for @Jmslistener Annotated Method to Complete in Junit

How to wait for @JMSListener annotated method to complete in JUnit

Well, I was hoping I can somehow hook into the Message Driven Pojos lifecycle to do this, but going through other SO questions about async code I came up with a solution based on CountDownLatch

  1. The @JMSListener annotaded method should call countDown() on a CountDownLatch after all the work is complete:

    @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. In the 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();
    }

The drawback of this solution is that you have to actually change your @JMSListener annotated method specifically for the integration test

junit spring jms listener

Wrap your listener bean in a proxy (for the test case) and use a latch and verify that the object received is what you expected...

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { So48033124Application.class, So48033124ApplicationTests.TestConfig.class })
public class So48033124ApplicationTests {

@Autowired
private JmsTemplate template;

@Test
public void test() throws Exception {
Foo foo = new Foo("bar");
this.template.convertAndSend("foo", foo);
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received).isEqualTo(foo);
}

@Configuration
public static class TestConfig {

private static final CountDownLatch latch = new CountDownLatch(1);

private static Object received;

@Bean
public static BeanPostProcessor listenerWrapper() {
return new BeanPostProcessor() {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyListener) {
MethodInterceptor interceptor = new MethodInterceptor() {

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
if (invocation.getMethod().getName().equals("listen")) {
received = invocation.getArguments()[0];
latch.countDown();
}
return result;
}

};
if (AopUtils.isAopProxy(bean)) {
((Advised) bean).addAdvice(interceptor);
return bean;
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvice(interceptor);
return proxyFactory.getProxy();
}
}
else {
return bean;
}
}

};
}

}

}

EDIT

The above is based on Spring Framework 5 or later which uses Java 8 and provides default implementations for both BeanPostProcessor methods.

If you are using an earlier version of Spring you will also need

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

The BPP should be static too.

Writing tests to verify received msg in jms listener (Spring-Boot)



@SpringBootApplication
public class So42803627Application {

public static void main(String[] args) {
SpringApplication.run(So42803627Application.class, args);
}

@Autowired
private JmsTemplate jmsTemplate;

@JmsListener(destination = "foo")
public void handle(String in) {
this.jmsTemplate.convertAndSend("bar", in.toUpperCase());
}

}

and

@RunWith(SpringRunner.class)
@SpringBootTest
public class So42803627ApplicationTests {

@Autowired
private JmsTemplate jmsTemplate;

@Test
public void test() {
this.jmsTemplate.convertAndSend("foo", "Hello, world!");
this.jmsTemplate.setReceiveTimeout(10_000);
assertThat(this.jmsTemplate.receiveAndConvert("bar")).isEqualTo("HELLO, WORLD!");
}

}

Junit @value annotated field usage Java

The below solution worked for me. I used ReflectionTestUtils to set value to property field in each test case method.

ReflectionTestUtils.setField(classToTest, "skipUnderwriting", Boolean.TRUE);

Spring Boot JMSListener blocked by other JMSListener for different destination

It looks like you are using transactions, the transaction won't commit until the @JmsListener method exits so the other consumer won't see it.

You can't use transactions for this use case.

Hence the @Async works because the send will be performed in a different transaction.



Related Topics



Leave a reply



Submit