为什么其他测试 classes 需要 DirtiesContext 来使用 JMS Listener 模拟 class 的 bean 依赖性
Why DirtiesContext is needed on other test classes to mock bean dependency for class with JMS Listener
上下文
具有 Rest 端点和 JMS AMQ 侦听器的 Spring 启动应用程序
观察到的测试行为
测试类 运行很好,不需要单独的DirtiesContext但是当整个测试套件类是运行时,观察到以下行为-
- 模拟 JMS 消费者测试的 bean 依赖项需要较早的测试 类 具有 DirtiesContext 注释。
- 模拟 RestControllers 的 bean 依赖性似乎与 JMS Listener 的工作方式不同,即在早期测试中不需要 DirtiesContext 类
我创建了一个简单的 Spring 应用程序来重现 Spring 上下文行为 我需要帮助理解 - https://github.com/ajaydivakaran/spring-dirties-context
发生这种情况的原因是没有 @DirtiesContext
Spring 将保留上下文以供共享相同设置的其他测试重用(阅读 [= 中有关上下文缓存的更多信息) 20=]).这对于您的设置来说并不理想,因为您有一个消息监听器,因为现在多个 Spring 上下文可以保持活动状态并且 窃取 您使用 JmsTemplate
.
使用 @DirtiesContext
确保停止应用程序上下文,因此此上下文之后 不活动 并且无法使用消息:
from @DirtiesContext:
Test annotation which indicates that the {@link org.springframework.context.ApplicationContext ApplicationContext} *
associated with a test is dirty and should therefore be
closed and removed from the context cache.
出于性能方面的原因,我尽量不要太频繁地使用 @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();
}
}
}
对应的测试:
@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()
);
}
}
和上下文初始值设定项:
public class DestinationValueInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("consumer.destination=" + UUID.randomUUID().toString()).applyTo(applicationContext);
}
}
我已经为您的项目提供了一个 small PR,您可以在日志中看到这一点,不同的应用程序上下文正在使用您的消息,因此您无法验证是否在您编写测试的应用程序上下文。
上下文
具有 Rest 端点和 JMS AMQ 侦听器的 Spring 启动应用程序
观察到的测试行为
测试类 运行很好,不需要单独的DirtiesContext但是当整个测试套件类是运行时,观察到以下行为-
- 模拟 JMS 消费者测试的 bean 依赖项需要较早的测试 类 具有 DirtiesContext 注释。
- 模拟 RestControllers 的 bean 依赖性似乎与 JMS Listener 的工作方式不同,即在早期测试中不需要 DirtiesContext 类
我创建了一个简单的 Spring 应用程序来重现 Spring 上下文行为 我需要帮助理解 - https://github.com/ajaydivakaran/spring-dirties-context
发生这种情况的原因是没有 @DirtiesContext
Spring 将保留上下文以供共享相同设置的其他测试重用(阅读 [= 中有关上下文缓存的更多信息) 20=]).这对于您的设置来说并不理想,因为您有一个消息监听器,因为现在多个 Spring 上下文可以保持活动状态并且 窃取 您使用 JmsTemplate
.
使用 @DirtiesContext
确保停止应用程序上下文,因此此上下文之后 不活动 并且无法使用消息:
from @DirtiesContext:
Test annotation which indicates that the {@link org.springframework.context.ApplicationContext ApplicationContext} * associated with a test is dirty and should therefore be closed and removed from the context cache.
出于性能方面的原因,我尽量不要太频繁地使用 @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();
}
}
}
对应的测试:
@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()
);
}
}
和上下文初始值设定项:
public class DestinationValueInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("consumer.destination=" + UUID.randomUUID().toString()).applyTo(applicationContext);
}
}
我已经为您的项目提供了一个 small PR,您可以在日志中看到这一点,不同的应用程序上下文正在使用您的消息,因此您无法验证是否在您编写测试的应用程序上下文。