Бины-имитации и бины-шпионы
При выполнении тестов иногда необходимо имитировать определенные компоненты в контексте приложения. Например, у вас может быть фасад для некоторой удаленной службы, которая недоступна во время разработки. Имитирование также может быть полезно, если необходимо имитировать сбои, которые трудно вызвать в реальном окружении.
Spring Boot содержит аннотацию @MockBean
, которую можно использовать для определения Mockito-имитации для бина внутри ApplicationContext
. Можно использовать аннотацию для добавления новых бинов или замены одного существующего определения бина. Аннотацию можно использовать непосредственно для тестовых классов, для полей внутри теста или для классов и полей с аннотацией @Configuration
. При использовании с полем, экземпляр созданного объекта-имитации также внедряется. Бины-имитации автоматически сбрасываются после выполнения каждого тестового метода.
Если тест использует одну из тестовых аннотаций Spring Boot (например, @SpringBootTest
), эта функция автоматически активируется. Чтобы использовать эту функцию с другим типом организации, необходимо явным образом добавить слушатели, как показано в следующем примере:
@ContextConfiguration(classes = MyConfig.class)
@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })
class MyTests {
// ...
}
@ContextConfiguration(classes = [MyConfig::class])
@TestExecutionListeners(
MockitoTestExecutionListener::class,
ResetMocksTestExecutionListener::class
)
class MyTests {
// ...
}
В следующем примере существующий бин RemoteService
заменяется имитационной реализацией:
@SpringBootTest
class MyTests {
@Autowired
private Reverser reverser;
@MockBean
private RemoteService remoteService;
@Test
void exampleTest() {
given(this.remoteService.getValue()).willReturn("spring");
String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps");
}
}
@SpringBootTest
class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) {
@Test
fun exampleTest() {
given(remoteService.value).willReturn("spring")
val reverse = reverser.reverseValue // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps")
}
}
@MockBean
нельзя использовать для имитирования логики работы бина, который выполняется во время обновления контекста приложения. К моменту выполнения теста обновление контекста приложения уже будет завершено, а настраивать имитируемую логику работы будет уже поздно. В этой ситуации мы рекомендуем использовать метод, помеченный аннотацией @Bean
, для создания и конфигурирования объекта-имитации.Кроме того, можно использовать аннотацию @SpyBean
, чтобы обернуть любой существующий бин объектом spy
из Mockito.
final
. Это останавливает корректную работу фреймворка Mockito, поскольку он не может имитировать или следить за final
методами в своей конфигурации по умолчанию. Если необходимо имитировать или следить за таким бином, сконфигурируйте Mockito на использование встроенного конструктора объектов-имитаций, добавив org.mockito:mockito-inline
в тестовые зависимости вашего приложения. Это позволить Mockito имитировать и следить за final
методами.@MockBean
или @SpyBean
влияет на ключ кэша, что, скорее всего, увеличит количество контекстов.@SpyBean
, чтобы следить за бинами с методами, аннотированными @Cacheable
, которые ссылаются на параметры по имени, ваше приложение должно быть скомпилировано с параметром -parameters
. Таким образом, инфраструктура кэширования гарантированно получит доступ к именам параметров после того, как бин будет обнаружен.@SpyBean
используется для слежки за проксируемым в Spring бином, в некоторых ситуациях может понадобиться удалить Spring-прокси, например, при установке ожидаемых событий с помощью given
или when
. Для этого используется AopTestUtils.getTargetObject(yourProxiedSpy)
.Автоконфигурируемые тесты
Система автоконфигурации в Spring Boot отлично работает с приложениями, но иногда может быть слишком избыточной для тестов. Зачастую практично загружать только те части конфигурации, которые необходимы для тестирования "слоя" приложения. Например, может возникнуть необходимость убедиться, что контроллеры Spring MVC корректно отображают URL-адреса, но при этом не требуется вовлекать вызовы базы данных в эти тесты, или же может потребоваться протестировать JPA-сущности, но веб-уровень при выполнении этих тестов не представляет интереса.
Модуль spring-boot-test-autoconfigure
содержит ряд аннотаций, которые можно использовать для автоматической конфигурации таких "слоев". Каждая из них работает одинаково, представляя собой аннотацию вида @…Test
, которая загружает ApplicationContext
, и одну или несколько аннотаций вида @AutoConfigure…
,которые можно использовать для настройки параметров автоконфигурации.
@…Test
предусматривают атрибут excludeAutoConfiguration
. В качестве альтернативы можно использовать аннотацию @ImportAutoConfiguration#exclude
.@…Test
в один тест не поддерживаются. Если требуется наличие нескольких "слоев", выберите одну из аннотаций вида @…Test
и добавьте аннотации вида @AutoConfigure…
других "слоев" вручную.@AutoConfigure…
со стандартной аннотацией @SpringBootTest
. Можно использовать эту комбинацию, если вы не заинтересованы в создании слоев вашего приложения, но вам требуются некоторые из автоматически сконфигурированных тестовых бинов.Автоконфигурируемые тесты JSON
Чтобы убедиться, что сериализация и десериализация JSON-объектов работает предполагаемым образом, можно использовать аннотацию @JsonTest
. Аннотация @JsonTest
автоматически конфигурирует доступный поддерживаемый JSON-сопоставитель, который может быть одной из следующих библиотек:
-
ObjectMapper
из Jackson, любые бины с аннотацией@JsonComponent
и любыеModules
из Jackson -
Gson
-
Jsonb
@JsonTest
, можно найти в приложении к документации.
Если необходимо сконфигурировать элементы автоконфигурации, можно использовать аннотацию @AutoConfigureJsonTesters
.
Spring Boot содержит вспомогательные классы на основе AssertJ, которые работают с библиотеками JSONAssert и JsonPath, предназначенные для проверки корректного предполагаемого отображения JSON. Классы JacksonTester
, GsonTester
, JsonbTester
и BasicJsonTester
можно использовать для библиотек Jackson, Gson, Jsonb и Strings соответственно. Любые вспомогательные поля тестового класса могут быть помечены аннотацией @Autowired
при использовании аннотации @JsonTest
. В следующем примере показан тестовый класс для библиотеки Jackson:
@JsonTest
class MyJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json;
@Test
void serialize() throws Exception {
VehicleDetails details = new VehicleDetails("Honda", "Civic");
// Утверждение для файла ".json" в том же пакете, что и тест
assertThat(this.json.write(details)).isEqualToJson("expected.json");
// Или используем утверждения на основе JSON-пути
assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
}
@Test
void deserialize() throws Exception {
String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
}
}
@JsonTest
class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) {
@Test
fun serialize() {
val details = VehicleDetails("Honda", "Civic")
// Утверждение для файла ".json" в том же пакете, что и тест
assertThat(json.write(details)).isEqualToJson("expected.json")
// Или используем утверждения на основе JSON-пути
assertThat(json.write(details)).hasJsonPathStringValue("@.make")
assertThat(json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda")
}
@Test
fun deserialize() {
val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"
assertThat(json.parse(content)).isEqualTo(VehicleDetails("Ford", "Focus"))
assertThat(json.parseObject(content).make).isEqualTo("Ford")
}
}
initFields
вспомогательного класса в вашем методе, аннотированным @Before
, если аннотация @JsonTest
не используется.Если вспомогательные классы Spring Boot на основе AssertJ используются для добавления утверждения для значения числа в заданном JSON-пути, можно и не использовать isEqualTo
в зависимости от типа. Вместо этого можно использовать функцию satisfies
в AssertJ для добавления утверждения соответствия значения заданному условию. Например, в следующем примере добавлено утверждение, что фактическое число является плавающим значением, близким к 0.15
в пределах смещения 0.01
.
@Test
void someTest() throws Exception {
SomeObject value = new SomeObject(0.152f);
assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
@Test
fun someTest() {
val value = SomeObject(0.152f)
assertThat(json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies(ThrowingConsumer { number ->
assertThat(number.toFloat()).isCloseTo(0.15f, within(0.01f))
})
}
Автоконфигурируемые тесты Spring MVC
Чтобы протестировать контроллеры Spring MVC на предмет предусмотренной работоспособности используйте аннотацию @WebMvcTest
. Аннотация @WebMvcTest
автоматически конфигурирует инфраструктуру Spring MVC и ограничивает сканируемые бины до @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, GenericConverter
, Filter
, HandlerInterceptor
, WebMvcConfigurer
, WebMvcRegistrations
и HandlerMethodArgumentResolver
. Обычные бины с аннотациями @Component
и @ConfigurationProperties
не подлежат сканированию при использовании аннотации @WebMvcTest
. Для добавления бинов, помеченных аннотацией @ConfigurationProperties
, можно использовать аннотацию @EnableConfigurationProperties
.
Module
из Jackson, можно импортировать дополнительные классы конфигурации, используя аннотацию @Import
в своем тесте.Зачастую аннотация @WebMvcTest
ограничивается одним контроллером и используется в сочетании с аннотацией @MockBean
, чтобы передавать имитационные реализации для необходимых взаимодействующих объектов.
@WebMvcTest
также автоматически конфигурирует MockMvc
. Mock MVC предусматривает эффективный способ быстрого тестирования контроллеров MVC без необходимости запуска полноценного HTTP-сервера.
MockMvc
без аннотации @WebMvcTest
(а например, с аннотацией @SpringBootTest
), аннотировав его с помощью @AutoConfigureMockMvc
. В следующем примере используется MockMvc
:@WebMvcTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Honda Civic"));
}
}
@WebMvcTest(UserVehicleController::class)
class MyControllerTests(@Autowired val mvc: MockMvc) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Honda Civic"))
}
}
@AutoConfigureMockMvc
.Если вы используете HtmlUnit и Selenium, автоконфигурация также предусматривает бин WebClient
для HtmlUnit и/или бин WebDriver
для Selenium. В следующем примере используется HtmlUnit:
@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {
@Autowired
private WebClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
}
}
@WebMvcTest(UserVehicleController::class)
class MyHtmlUnitTests(@Autowired val webClient: WebClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot")).willReturn(VehicleDetails("Honda", "Civic"))
val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html")
assertThat(page.body.textContent).isEqualTo("Honda Civic")
}
}
WebDriver
в специальную "область доступности", чтобы обеспечить выход драйвера после каждого теста и внедрение нового экземпляра. Если такая логик работы лишняя, можно добавить аннотацию @Scope("singleton")
в определение вашего WebDriver
с аннотацией @Bean
.webDriver
, созданная Spring Boot, заменит любую определенную пользователем область доступности с тем же именем. Если определена собственная область доступности webDriver
, то можно обнаружить, что она перестает работать при использовании аннотации @WebMvcTest
.Если в classpath есть Spring Security, аннотация @WebMvcTest
также будет сканировать бины WebSecurityConfigurer
. Вместо того чтобы полностью отключать средства безопасности для таких тестов, можно использовать средства поддержки тестов из Spring Security.
Автоконфигурируемые тесты Spring WebFlux
Чтобы убедиться, что контроллеры Spring WebFlux работают так, как предполагается, можно использовать аннотацию @WebFluxTest
. Аннотация @WebFluxTest
автоматически конфигурирует инфраструктуру Spring WebFlux и ограничивает сканируемые бины до @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, GenericConverter
, WebFilter
и WebFluxConfigurer
. Обычные бины с аннотациями @Component
и @ConfigurationProperties
не подлежат сканированию при использовании аннотации @WebFluxTest
. Для добавления бинов, помеченных аннотацией @ConfigurationProperties
, можно использовать аннотацию @EnableConfigurationProperties
.
Module
из библиотеки Jackson, можно импортировать дополнительные конфигурационные классы, используя аннотацию @Import
в тесте.Зачастую аннотация @WebFluxTest
ограничивается одним контроллером и используется в сочетании с аннотацией @MockBean
, чтобы передавать имитационные реализации для необходимых взаимодействующих объектов.
Аннотация @WebFluxTest
также автоматически конфигурирует WebTestClient
, который предусматривает эффективное средство быстрого тестирования контроллеров WebFlux без необходимости запуска полноценного HTTP-сервера.
WebTestClient
без аннотации @WebFluxTest
(а например, с аннотацией @SpringBootTest
), аннотировав его с помощью @AutoConfigureWebTestClient
. В следующем примере показан класс, который использует аннотацию @WebFluxTest
и WebTestClient
:@WebFluxTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private WebTestClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Honda Civic");
}
}
@WebFluxTest(UserVehicleController::class)
class MyControllerTests(@Autowired val webClient: WebTestClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Honda Civic")
}
}
WebTestClient
в имитируемом веб-приложении на данный момент работает только в WebFlux.@WebFluxTest
не может обнаруживать маршруты, зарегистрированные при помощи функционального веб-фреймворка. Для тестирования бинов RouterFunction
в контексте рассмотрите возможность самостоятельного импорта RouterFunction
с помощью аннотации @Import
или с помощью аннотации @SpringBootTest
.@WebFluxTest
не может обнаруживать кастомную конфигурацию безопасности, зарегистрированную как @Bean
типа SecurityWebFilterChain
. Чтобы включить её в свой тест, нужно импортировать конфигурацию, регистрирующую бин, с помощью аннотации @Import
или аннотации @SpringBootTest
.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ