Введение компонента в контекст теста WebMVC: @AutoConfigureMockMvc нельзя использовать в сочетании с аннотацией @компонента @Конфигурация

#java #spring-boot #spring-test #mockmvc #junit-jupiter

Вопрос:

Для моего весеннего теста Java я решил подделать системные часы, чтобы упростить тестирование

У меня есть тестовый компонент, который наследует общий базовый класс

 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc(print = MockMvcPrint.LOG_DEBUG)
@TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        value = {FlywayTestExecutionListener.class}
)
@FlywayTest
@Getter(AccessLevel.PROTECTED)
public abstract class AbstractMockMvcTest {}


@Configuration
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@WithMockUser(username = "junit", authorities = {}, roles = {})
class RolesControllerTest extends AbstractMockMvcTest {

    private final Instant fixedInstant = Instant.from(OffsetDateTime.of(2021, 6, 2, 15, 54, 0, 0, ZoneOffset.ofHours(2)));

    @Bean
    Clock fakeClock() {
        return Clock.fixed(fixedInstant, ZoneId.ofOffset("UTC", ZoneOffset.ofHours(2)));
    }

}


@SpringBootApplication
public class Application {

   
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }


    @ConditionalOnMissingBean(Clock.class)
    @Bean
    public Clock systemClock() {
        return Clock.systemUTC();
    }
}
 

Сама идея заключается в том, что мое приложение объявляет основные часы, которые используются каждым компонентом, которому необходимо получить доступ ко времени (например getClock().instant() , или Instant.now(getClock()) ). Издеваясь над часами, я облегчаю тестирование.

Но я нашел более естественным использование весенних бобов, чем Мокито. Clock В Java есть методы для правильного моделирования часов

Наилучшей практикой для приложений является передача часов в любой метод, для которого требуется текущий момент времени. Структура внедрения зависимостей — один из способов достижения этой цели:

 public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
  }
 
 

Этот подход позволяет использовать альтернативные часы, такие как фиксированные или смещенные, во время тестирования.

Но когда я пытаюсь запустить тест, я натыкаюсь на это

 @AutoConfigureMockMvc cannot be used in combination with the @Component annotation @Configuration
 

Есть ли разумная идея объявить дополнительные основные компоненты в веб-тесте MVC?

Ответ №1:

Настройте статический внутренний @TestConfiguration класс, в котором определяется компонент:

 // Remove the @Configuration annotation here
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@WithMockUser(username = "junit", authorities = {}, roles = {})
@ContextConfiguration(classes = RolesControllerTest.TestConfiguration.class) // Add a ContextConfiguration if the test configuration isn't automatically used.
class RolesControllerTest extends AbstractMockMvcTest {

    private final Instant fixedInstant = Instant.from(OffsetDateTime.of(2021, 6, 2, 15, 54, 0, 0, ZoneOffset.ofHours(2)));

    @TestConfiguration
    public static class TestConfig {

        @Bean
        Clock fakeClock() {
            return Clock.fixed(fixedInstant, ZoneId.ofOffset("UTC", ZoneOffset.ofHours(2)));
        }
    }
}
 

Альтернативой является объявление a @MockBean private Clock clock и использование mockito для возврата фиксированного момента.

Комментарии:

1. Не уверен, что это полное и рабочее решение. Я имею в виду, что в моем случае у меня возникли трудности с тем, что класс @TestConfiguration отключил весь контекст Spring, эффективно удалив PlatformTransactionManager, который мне требуется. Я опубликую другой ответ, но, скорее всего, приму это

Ответ №2:

Я обнаружил, что работает следующее. Это несколько альтернативное решение @Michiel’s. Изначально (и до того, как был опубликован ответ) Я попробовал их подход к классу внутренней конфигурации, но наткнулся на другую проблему: класс внутренней конфигурации, либо @Configuration или @TestConfiguration , удалил весь контекст, и никаких других компонентов не было видно. Включая PlatformTransactionManager то, что не могло быть подключено автоматически

Для меня сработало следующее

 @Configuration
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "classpath:..", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@WithMockUser(username = "junit", authorities = {}, roles = {})
class RolesControllerTest extends AbstractMockMvcTest {

    private final Instant fixedInstant = Instant.from(OffsetDateTime.of(2021, 6, 2, 15, 54, 0, 0, ZoneOffset.ofHours(2)));

    @TestConfiguration
    @Import(Application.class)
    static class ClockConfigurer {
        @Bean
        @Primary
        Clock fakeClock() {
            return Clock.fixed(fixedInstant, ZoneId.ofOffset("UTC", ZoneOffset.ofHours(2)));
        }
    }

}
 

В моем случае мне пришлось добавить @Import аннотацию к классу конфигурации теста, указав на основную конфигурацию.

Я изначально использовал @Import в тестовом классе, прокомментировал его, но каждый раз контекст был ошибочным. Мне также пришлось пометить часы как @Primary для работы с автопроводным разрешением по типу

Это помогло. Все еще благодаря @Michiel