Почему DirtiesContext необходим в других тестовых классах для имитации зависимости bean-компонента для класса с JMS Listener - PullRequest
0 голосов
/ 20 июня 2020

Контекст

Приложение Spring Boot с конечной точкой Rest и прослушивателем JMS AMQ

Наблюдается поведение теста

Классы тестов работают нормально без отдельного использования DirtiesContext, но при запуске всего набора тестовых классов наблюдается следующее поведение:

  1. Мокинг зависимости bean-компонента для теста JMS Consumer требует, чтобы предыдущие тестовые классы были имеют аннотацию DirtiesContext.
  2. Имитация зависимости bean-компонентов для RestControllers, похоже, работает иначе, чем прослушиватель JMS, т.е. не нуждается в DirtiesContext в более ранних тестовых классах.

Я создал простое приложение Spring для воспроизведения поведения контекста Spring, мне нужна помощь в понимании - https://github.com/ajaydivakaran/spring-dirties-context

1 Ответ

1 голос
/ 22 июня 2020

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

...