为什么其他测试类需要 DirtiesContext 来模拟具有 JMS Listener 的类的 bean 依赖关系

Aja*_*jay 3 spring spring-test spring-boot

语境

具有 Rest 端点和 JMS AMQ 侦听器的 Spring Boot 应用程序

观察到的测试行为

测试类运行良好,不需要单独的 DirtiesContext,但是当运行整个测试类套件时,会观察到以下行为 -

  1. JMS Consumer 测试的 bean 依赖关系的模拟要求早期的测试类具有 DirtiesContext 注释。
  2. RestControllers 的 bean 依赖关系的模拟似乎与 JMS Listener 的工作方式不同,即早期的测试类不需要 DirtiesContext

我创建了一个简单的 Spring 应用程序来重现我需要帮助理解的 Spring 上下文行为 - https://github.com/ajaydivakaran/spring-dirties-context

rie*_*pil 5

发生这种情况的原因是,如果没有@DirtiesContextSpring,将保留上下文以供共享相同设置的其他测试重用(请阅读Spring 文档中有关上下文缓存的更多信息)。这对于您的设置来说并不理想,因为您有一个消息监听器,因为现在多个 Spring 上下文可以保持活动状态并窃取您使用JmsTemplate.

使用@DirtiesContext确保停止应用程序上下文,因此此上下文之后不再存在,并且无法使用消息:

来自@DirtiesContext:

测试注释表明与测试关联的 {@link org.springframework.context.ApplicationContext ApplicationContext} * 是脏的,因此应该关闭并从上下文缓存中删除。

出于性能原因,我会尝试不要过于频繁地使用@DirtiesContext,而是确保 JMS 目标对于您在测试期间启动的每个上下文都是唯一的。您可以通过将该destination值外包到配置文件 ( application.properties) 并随机填充该值(例如使用ContextInitializer )来实现此目的。

第一个(简单)实现可能如下所示:

@AllArgsConstructor
@Service
public class Consumer {
    private EnergeticGreeter greeter;
    private MessageRepository repository;
    private ApplicationContext applicationContext;

    @JmsListener(destination = "${consumer.destination}")
    public void consume(
            @Header(name = JmsHeaders.MESSAGE_ID, required = false) String messageId,
            TextMessage textMessage) {

        System.out.println("--- Consumed by context: " + applicationContext.toString());

        if ("Ahem hello!!".equals(greeter.welcome().getContent())) {
            repository.save();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

相应的测试:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = DestinationValueInitializer.class)
public class JMSConsumerIntegrationTest {

    @Autowired
    private JmsTemplate jmsTemplate;

    @Value("${consumer.destination}")
    private String destination;

    @Autowired
    private ApplicationContext applicationContext;

    @MockBean
    private EnergeticGreeter greeter;

    @MockBean
    private MessageRepository repository;

    //Todo - To get all tests in this project to pass when entire test suite is run look at Todos added.
    @Test
    public void shouldInvokeRepositoryWhenGreetedWithASpecificMessage() {
        when(greeter.welcome()).thenReturn(new Message("Ahem hello!!"));

        System.out.println("--- Send from context: " + applicationContext.toString());

        jmsTemplate.send(destination, session -> session.createTextMessage("hello world"));

        Awaitility.await().atMost(10, TimeUnit.SECONDS).untilAsserted(
                () -> verify(repository, times(1)).save()
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

和上下文初始值设定项:

public class DestinationValueInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        TestPropertyValues.of("consumer.destination=" + UUID.randomUUID().toString()).applyTo(applicationContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

我为您的项目提供了一个小型 PR,您可以在日志中看到这一点,即不同的应用程序上下文正在消耗您的消息,因此您无法验证是否在您编写测试的应用程序上下文上调用了存储库。