Search code examples
springspring-bootmockitospring-aopspring-test

Why are my MockBeans and MockRestServiceServer not returning proper responses when testing JMS Listener in Spring Boot


I am having an issue when trying to integration test my JMS listener with Mockito and MockRestServiceServer. Even if I'm using the correct Mockito.when annotations, they are coming up as null, and the MockRestServiceServer is acting as if it isn't being called. If I switch instead to test against the myService component that the jms listener calls, the mocks and the MockRestServiceServer calls are working as expected, which is puzzling. I am connecting to an embedded ActiveMQ broker for the test and I am using Spring Boot 2.2.8.RELEASE and JDK 8.x if that helps.

Here is the JMS Listener Class

@Component
public class MyJmsListener {

    @Autowired
    private MyService myService;

    @JmsListener(
            destination = "${jms.queue}",
            containerFactory = "myJmsListenerContainerFactory"
    )
    public void receive(Message<String> message) {
        myService.process(message);
    }
}

Here is the JMS Listener Test Class

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class JmsListenerTest {
...
    @MockBean
    private AuthorizationService authorizationService;
...
    @Autowired
    private MockRestServiceServer mockRestServiceServer;

    @Autowired
    private JmsTemplate listenerTestJmsTemplate;

    @Value("${jms.queue}")
    private String testDestination;
...
   @Test
    public void testListener() throws IOException, URISyntaxException, InterruptedException {
        //ARRANGE
        String payloadPath = "classpath:payloads/listenerPayload.json";
        String payload = new String(Files.readAllBytes(ResourceUtils.getFile(payloadPath).toPath()));
        String testAuth = "auth";
        Mockito.when(authorizationService.generateTicket(Mockito.any(Headers.class), Mockito.eq("9130353887051456")))
                .thenReturn(testAuth);
        String extPayloadPath = "classpath:payloads/revokeCancelAutoRenewRequestApi.json";
        String extPayload = new String(Files.readAllBytes(ResourceUtils.getFile(extPayloadPath).toPath()));
        mockRestServiceServer.expect(ExpectedCount.once(), MockRestRequestMatchers.requestTo(new URI("/test/v3/subscriptions/400367048/something")))
                             .andExpect(MockRestRequestMatchers.content().string(extPayload))
                             .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, testAuth))
                             .andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK));
        //ACT
        listenerTestJmsTemplate.convertAndSend(testDestination, payload);
        //ASSERT
        mockRestServiceServer.verify();
        Assert.assertTrue(JmsListenerWrapperConfiguration.latch.await(5, TimeUnit.SECONDS));
    }
...
}

I have a JmsListenerWrapperConfiguration that will allow me to wrap the countdown latch into the jms listener.

@Configuration
@Profile("test")
public class JmsListenerWrapperConfiguration {

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

    @Bean
    public JmsTemplate listenerTestjmsTemplate(ActiveMQConnectionFactory activeMQConnectionFactory){
        JmsTemplate jmsTemplate = new JmsTemplate(activeMQConnectionFactory);
        return jmsTemplate;
    }

    /**
     * Wrap the JMS Listeners with a count down latch that will allow us to unit test them.
     * @return The bean post processor that will wrap the JMS Listener.
     */
    @Bean
    public static BeanPostProcessor listenerWrapper() {
        return new BeanPostProcessor() {

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

                        @Override
                        public Object invoke(MethodInvocation invocation) throws Throwable {
                            Object result = invocation.proceed();
                            if (invocation.getMethod().getName().equals("listen")) {
                                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;
                }
            }

        };
    }
}

The MockRestServiceServer configuration is defined here.

@Configuration
@Profile("test")
public class MockRestServiceServerConfiguration {
    
    @Bean
    public MockRestServiceServer mockRestServiceServer(RestTemplate restTemplate) {
        MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(restTemplate);
        MockRestServiceServer server = builder.bufferContent().build();
        return server;
    }
}

The error that I see is as follows.

java.lang.AssertionError: Further request(s) expected leaving 1 unsatisfied expectation(s).
0 request(s) executed.
    at org.springframework.test.web.client.AbstractRequestExpectationManager.verify(AbstractRequestExpectationManager.java:159)
    at org.springframework.test.web.client.MockRestServiceServer.verify(MockRestServiceServer.java:116)

Update

I've been debugging and of course the test is running on thread[main], whereas the JMS listener is running on thread[DefaultMessageListenerContainer-1], so my question then becomes, what should we do with Mockito mocking when the mocks/verifications need to be used by separate threads?


Solution

  • It turns out that the MockRestServiceServer needed to verify after the latch is awaiting as shown in this code below.

    Assert.assertTrue(JmsListenerWrapperConfiguration.latch.await(5, TimeUnit.SECONDS));
    mockRestServiceServer.verify();