I want to unit test a simple jms listener code below
@Component
public class NotificationReader {
@JmsListener(destination = "myAppQ")
public void receiveMessage(NotificationMessage notificationMessage) {
System.out.println("Received <" + notificationMessage.getStaffNumber() + ">");
}
}
From the junit, I am using jmsTemplate to pump the message into Active MQ.
I want to test if the jms listener got invoked.
I saw few solutions ( using counters ) like How to wait for @JMSListener annotated method to complete in JUnit , which actually changes the listener code just for testing purpose which I dont want to do.
Is there any other alternative ?
Tried with configuration as suggested in the answer .
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestNotificationReader {
@Autowired
JmsTemplate jmsTemplate;
@Value("${outbound.endpoint}")
private String destination;
@Test
public void contextLoads() {
}
@Test
public void testPutToQ() {
NotificationMessage notificationMessage = new NotificationMessage();
notificationMessage.setStaffNumber("100");
notificationMessage.setWorkflowType("TYPE");
notificationMessage.setWorkflowId("100");
notificationMessage.setWorkflowDescription("Test From Queue");
jmsTemplate.convertAndSend(destination, notificationMessage);
jmsTemplate.setReceiveTimeout(10000);
try {
TestConfig.latch.await(10, TimeUnit.SECONDS);
NotificationMessage temp = (NotificationMessage) TestConfig.received;
System.out.println(" temp.getStaffNumber() " + temp.getStaffNumber());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@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 NotificationReader) {
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
if (invocation.getMethod().getName().equals("receiveMessage")) {
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;
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
return bean;
}
};
}
}
}
Once I add the testConfig , the JMSTempate autowiring fails
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.jms.core.JmsTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.