Причина, по которой это происходит, заключается в том, что без @DirtiesContext
Spring останется контекстом для повторного использования для других тестов, использующих ту же настройку (подробнее о кэшировании контекста в документации 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();
}
}
}
соответствующий тест:
@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);
}
}
Я предоставил небольшой PR для вашего проекта, где вы можете видеть это в журналах, что другой контекст приложения использует ваше сообщение, и, следовательно, вы не можете проверить, что репозиторий был вызван в контексте приложения, в котором вы пишете свой тест.