#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