1

I am writing unit/integration test to check whether the JMS listener using IBM MQ is getting called correctly. There are two listeners in my IncomingDataListener. They are for two types of data comming in the same queue, processed by MyDataProcessorService and MyOtherDataProcessorService.

As can be seen below I have mocked the two services MyDataProcessorService and MyOtherDataProcessorService.

But I am not using JmsTemplate to send the message to my queue, to test whether the correct method is being called.

Instead I am reading the xml file directly by directly calling the recieveMessage() method and checking the correct data processor service is being called or not.

Do let me know whether I should rather be sending message using the JmsTemplate to the queue, rather than going ahead with this approach of directly calling the reciever?

@RunWith(SpringRunner.class)
@Import({IncomingDataListener.class, DefaultJmsListenerContainerFactory.class})
@ContextConfiguration(classes = JmsConfig.class)
@PropertySource("classpath:config/application-test.properties")
public class IncomingDataListenerTest {

    @Autowired
    private IncomingDataListener incomingDataListener;

    @MockBean
    private MyDataProcessorService myDataProcessorService;

    @MockBean
    private MyOtherDataProcessorService myOtherDataProcessorService;


    private String myDataFileString;
    
    @PostConstruct
    public void setup() throws IOException {
        myDataFileString = XmlUtils.readFile("FileHavingXMLData.xml");
    }

    @Test
    public void testProcessMyData() throws StaticDataValidationException {
        incomingDataListener.receiveMessage(myDataFileString);// Directly  I am calling the IncomingDataListener classes recieveMessage method, is it right or wrong??
        verify(myDataProcessorService, times(1)).process(any());
        verify(myOtherDataProcessorService, times(0)).processOther(any());
        
    }
}

Here is the config class, initially I thought of creating a JmsTemplate to send the message to the queue and then test at the listener side whether the correct processor is getting called!!

@Configuration
@PropertySource("classpath:config/application-test.properties")
@Slf4j
public class JmsConfig {

    @Value("${mq.jms.host}")
    private String host;

    @Value("${mq.jms.port}")
    private Integer port;

    @Value("${mq.jms.queue.manager}")
    private String queueManager;

    @Value("${mq.jms.channel}")
    private String channel;

    @Value("${mq.inbound.queue}")
    private String queue;

    @Value("${mq.timeout}")
    private Long timeout;

    @Bean
    public MQQueueConnectionFactory getConnectionFactory() {
        MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
        try {
            mqQueueConnectionFactory.setHostName(host);
            mqQueueConnectionFactory.setQueueManager(queueManager);
            mqQueueConnectionFactory.setChannel(channel);
        } catch (JMSException e){
            log.debug("Error creating Connection Factory");
        }
        return mqQueueConnectionFactory;
    }

    @Bean
    public JmsTemplate jmsTemplateFactory(MQQueueConnectionFactory mqQueueConnectionFactory) {
        JmsTemplate jmsTemplate = new JmsTemplate(mqQueueConnectionFactory);
        return jmsTemplate;
    }

    @Bean
    public JmsListenerContainerFactory queueContainer(MQQueueConnectionFactory mqQueueConnectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(mqQueueConnectionFactory);
        return factory;
    }
}

To my knowledge I have done all the things required, but I am getting this exception as Listed below.

java.lang.IllegalStateException: Failed to load ApplicationContext

    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
    at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.postProcessFields(MockitoTestExecutionListener.java:95)
    at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.injectFields(MockitoTestExecutionListener.java:79)
    at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.prepareTestInstance(MockitoTestExecutionListener.java:54)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'jmsListenerContainerFactory' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:814)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1282)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:297)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
    at org.springframework.jms.config.JmsListenerEndpointRegistrar.resolveContainerFactory(JmsListenerEndpointRegistrar.java:159)
    at org.springframework.jms.config.JmsListenerEndpointRegistrar.registerAllEndpoints(JmsListenerEndpointRegistrar.java:143)
    at org.springframework.jms.config.JmsListenerEndpointRegistrar.afterPropertiesSet(JmsListenerEndpointRegistrar.java:135)
    at org.springframework.jms.annotation.JmsListenerAnnotationBeanPostProcessor.afterSingletonsInstantiated(JmsListenerAnnotationBeanPostProcessor.java:210)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:912)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    ... 25 more

Please tell me how to come out of this conundrum.

John Doe
  • 2,752
  • 5
  • 40
  • 58

2 Answers2

3

Your factory has a non-standard bean name; the default is jmsListenerContainerFactory; if you name it something else, you must specify the bean name on the @JmsListener annotation.

Either

   @Bean
    public JmsListenerContainerFactory jmsListenerContainerFactory(MQQueueConnectionFactory mqQueueConnectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(mqQueueConnectionFactory);
        return factory;
    }

or

   @Bean("jmsListenerContainerFactory")
    public JmsListenerContainerFactory queueContainer(MQQueueConnectionFactory mqQueueConnectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(mqQueueConnectionFactory);
        return factory;
    }
    /**
     * The bean name of the {@link org.springframework.jms.config.JmsListenerContainerFactory}
     * to use to create the message listener container responsible for serving this endpoint.
     * <p>If not specified, the default container factory is used, if any.
     */
    String containerFactory() default "";
Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thanks Gary, how many times you have helped me!! Thanks always. Do you think I am doing it right, or I should send the message to the actual production queue and test it whether my processors are called correctly or not!! – John Doe Aug 20 '21 at 17:34
  • 1
    It depends on what your goals are; calling the listener directly is more of a unit test than an integration test but is quite valid. Actually sending a message to the broker would be an integration test, which is also a valid thing to do, to verify configuration is correct. So, both are useful. – Gary Russell Aug 20 '21 at 18:12
  • So in the same class as shown above I can configure the JmsTemplate to send message to the broker to do the Integration Test as well. Correct me if I am wrong. – John Doe Aug 20 '21 at 18:16
  • 1
    Yes; but most people would use an embedded broker (such as ActiveMQ) in tests; especially for CI builds where you don't want to depend on external servers. TestContainers is another option - looks like there's an IBM MQ container https://github.com/ibm-messaging/mq-container – Gary Russell Aug 20 '21 at 19:00
1

I think that one of following should work for you

  1. In the config file rename the method name queueContainer to jmsListenerContainerFactory
  2. In the config file use @Bean("jmsListenerContainerFactory") instead of the @Bean
  3. Change the type of the attribute to which you inject the factory to JmsListenerContainerFactory
  4. Change the name of the attribute to which you inject the factory to jmsListenerContainerFactory

You can learn more about how spring names beans and wires them to classes from the following sources:

Marek Żylicz
  • 429
  • 3
  • 8
  • Can only accept one answer, as Gary was first hence accepted him. But the links are very helpful – John Doe Aug 20 '21 at 17:49
  • Understandable ;-) – Marek Żylicz Aug 20 '21 at 17:50
  • I have upvoted your answer, also let me know what do you mean by "change the type of attribute to JmsListenerContainerFactory" and also the 4th point. I have a request to make always please kindly back up the answer with the code sample. They elaborate everything. – John Doe Aug 20 '21 at 18:01
  • 1
    Points 3 and 4 might not be applicable for your specific case because probably you do not do Autowiring of the factory yourself. Code examples are under point 4.1.1 and 4.1.3 of the second link https://www.baeldung.com/spring-annotations-resource-inject-autowire – Marek Żylicz Aug 20 '21 at 18:07