为什么在 Spring Boot 中测试 JMS Listener 时我的 MockBeans 和 MockRestServiceServer 没有返回正确的响应

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

我在尝试使用 MockitoMockRestServiceServer 集成测试我的 JMS 侦听器时遇到问题。即使我使用了正确的 Mockito.when 注释,它们也会显示为 null,并且 MockRestServiceServer 就像没有被调用一样。如果我改为针对 jms 侦听器调用的 myService 组件进行测试,模拟和 MockRestServiceServer 调用将按预期工作,这令人费解。我正在连接到嵌入式 ActiveMQ 代理进行测试,我正在使用 Spring Boot 2.2。8.RELEASE 和 JDK 8.x 如果有帮助的话。

这是 JMS 侦听器 Class

@Component
public class MyJmsListener {

    @Autowired
    private MyService myService;

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

这是 JMS 侦听器测试 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));
    }
...
}

我有一个 JmsListenerWrapperConfiguration,它允许我将倒计时闩锁包装到 jms 侦听器中。

@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;
                }
            }

        };
    }
}

这里定义了 MockRestServiceServer 配置。

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

我看到的错误如下。

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)

更新

我一直在调试,当然测试是 运行 在线程 [main] 上,而 JMS 侦听器是 运行 在线程 [DefaultMessageListenerContainer-1] 上,所以我的问题就变成了,当 mocks/verifications 需要被单独的线程使用时,我们应该如何处理 Mockito 模拟?

事实证明,MockRestServiceServer 需要在闩锁等待后进行验证,如下面的代码所示。

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