Біни-імітації та біни-шпигуни

При виконанні тестів іноді необхідно імітувати певні компоненти в контексті програми. Наприклад, у вас може бути фасад деякої віддаленої служби, яка недоступна під час розробки. Імітування також може бути корисним, якщо необхідно імітувати збої, які важко викликати в реальному оточенні. ApplicationContext. Можна використовувати інструкцію для додавання нових бінів або заміни одного існуючого визначення біну. Анотацію можна використовувати безпосередньо для тестових класів, для полів усередині тесту або для класів та полів з анотацією @Configuration. При використанні з полем екземпляр створеного об'єкта-імітації також впроваджується. Біни-імітації автоматично скидаються після виконання кожного тестового методу.

Якщо тест використовує одну з тестових анотацій Spring Boot (наприклад, @ SpringBootTest), ця функція автоматично активується. Щоб використовувати цю функцію з іншим типом організації, необхідно явно додати слухачі, як показано в наступному прикладі:

Java
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener; import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @ContextConfiguration(classes = MyConfig.class) @TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class }) class MyTests { // ... } 
Kotlin
import org.springframework. boot.test.mock.mockito.MockitoTestExecutionListener import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener import org.springframework.test.context.ContextConfiguration import org.springframework. ="fold-block">@ContextConfiguration(classes = [MyConfig::class]) @TestExecutionListeners( MockitoTestExecutionListener::class, ResetMocksTestExecutionListener::class ) class MyTests { // ... } 

У наступному прикладі існуючий бін RemoteService замінюється імітаційною реалізацією:

Java
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @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"); } } 
Kotlin
import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot .test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.MockBean @SpringBootTest class : RemoteService) { @Test fun exampleTest() { given(remoteService.value).willReturn("spring") val reverse = reverser.reverseValue // Calls injected RemoteService assertThat(reverse).isEqualTo("gnirp"); 
-Аннотацію @MockBean не можна використовувати для імітування логіки роботи бина, який виконується під час оновлення контексту програми. До моменту виконання тесту оновлення контексту програми вже буде завершено, а налаштовувати логіку роботи, що імітується, буде вже пізно. У цій ситуації ми рекомендуємо використовувати метод, позначений інструкцією @Bean, для створення та конфігурування об'єкта-імітації.

Крім того, можна використовувати інструкцію @SpyBean , щоб обернути будь-який існуючий бін об'єктом spy з Mockito.

Проксі з CGLib, наприклад, створені для бінів, що входять до область доступності, оголошують методи, що проксуються, як final. Це зупиняє коректну роботу фреймворку Mockito, оскільки він не може імітувати або стежити за final методами у своїй конфігурації за умовчанням. Якщо потрібно імітувати або стежити за таким біном, налаштуйте Mockito на використання вбудованого конструктора об'єктів-імітацій, додавши org.mockito:mockito-inline у тестові залежності вашої програми. Це дозволить Mockito імітувати і стежити за final методами.
Хоча тестовий фреймворк Spring кешує контексти додатків між тестами і повторно використовує контекст для тестів з однаковою конфігурацією використання анотацій @MockBean або @SpyBean впливає на ключ кеша, що, швидше за все, збільшить кількість контекстів.
Якщо ви використовуєте анотацію @SpyBean, щоб стежити за бінами з методами, анотованими @Cacheable, які посилаються на параметри на ім'я, ваша програма має бути скомпілювана з параметром -parameters. Таким чином, інфраструктура кешування гарантовано отримає доступ до імен параметрів після того, як бін буде виявлено. використовується для стеження за проксованим у 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. Можна використовувати цю комбінацію, якщо ви не зацікавлені в створенні шарів вашої програми, але вам потрібні деякі з автоматично конфігурованих тестових бінів. -об'єктів працює передбачуваним чином, можна використовувати інструкцію @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:

Java
import org.junit.jupiter .api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.json.JsonTest; import org.springframework.boot.test.json.JacksonTester; import static org.assertj.core.api.Assertions.assertThat; @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"); } } 
Kotlin
import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.json.JsonTest import org.springframework.boot.test.json.JacksonTester @JsonTest class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) { @Test fun 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(" } @Test fun deserialize() { val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}" assertThat(json.parse(content)).isEqualTo( VehicleDetails("Ford", "Focus")) assertThat(json.parseObject(content).make).isEqualTo("Ford") } } 
Допоміжні класи JSON також можна використовувати безпосередньо у стандартних модульних тестах. Для цього викличте метод initFields допоміжного класу у вашому методі, анотованому @Before, якщо анотація @JsonTest не використовується.

Якщо допоміжні класи Spring Boot на основі AssertJ використовуються для додавання затвердження значення числа в заданому JSON-шляху, можна і не використовувати isEqualTo залежно від типу. Натомість можна використовувати функцію satisfies в AssertJ для додавання затвердження відповідності значення заданій умові. Наприклад, в наступному прикладі додано твердження, що фактичне число є плаваючим значенням, близьким до 0.15 в межах зміщення 0.01.

Java
@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. )); } 
Kotlin
@Test fun someTest() { val value = SomeObject(0.152f) assertThat(json .write(value)).extractingJsonPathNumberValue("@.test.numberValue") .satisfies(ThrowingConsumer { number -> assertThat(number.toFloat()).isCloseTo(0.15f) 

Автоконфігуровані тести 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:

Java
import org. junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @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")); } } 
Kotlin
import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org .springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMeck .test.web.servlet.result.MockMvcResultMatchers @WebMvcTest(UserVehicleController::class) class MyControllerTests(@Autowired val mvc:MockMvc) @Test fun testExample() { given(userVehicleService.getVehicleDetails("sboot")) .willReturn(VehicleDetails("Honda", "Civic")) mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle .andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.content().string("Honda Civic")) } } 
Якщо необхідно конфігурувати елементи автоконфігурації (наприклад, якщо потрібно застосувати фільтри сервлетів), можна використовувати атрибути в анотації @AutoConfigureMockMvc .

Якщо ви використовуєте HtmlUnit та Selenium, автоконфігурація також передбачає бін WebClient для HtmlUnit та/або бін WebDriver для Selenium. У цьому прикладі використовується HtmlUnit:

Java
import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @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"); } } 
Kotlin
import com.gargoylesoftware.htmlunit.WebClient import com.gargoylesoftware.htmlunit.html.HtmlPage import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.mockito.BDDMockito. import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean @WebMvcTest(UserVehicleController::class) class MyHtmlUnitTests(@Autowired val webClient: WebClient) { @MockBean lateinit var userVehicleService: UserVehicleService @" .willReturn(VehicleDetails("Honda", "Civic")) val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html") assertThat(page.body.textContent).isEqualTo("Honda Civic") } 
За замовчуванням Spring Boots розміщує біни WebDriver у спеціальну "область доступності", щоб забезпечити вихід драйвера після кожного тесту та впровадження нового екземпляра. Якщо така логік роботи зайва, можна додати інструкцію @Scope("singleton") на визначення вашого WebDriver з анотацією @Bean.
Область доступності webDriver, створена Spring Boot, замінить будь-яку певну користувачем область доступності з тим же ім'ям. Якщо визначено власну область доступності webDriver, то можна виявити, що вона перестає працювати при використанні анотації @WebMvcTest.

Якщо в classpath є Spring Security, анотація @WebMvcTest також скануватиме біни WebSecurityConfigurer. Замість того, щоб повністю відключати засоби безпеки для таких тестів, можна використовувати засоби підтримки тестів з Spring Security. Іноді одного написання тестів у Spring MVC недостатньо; Spring Boot допоможе запустити повнофункціональні наскрізні тести з використанням реального сервера.

Автоконфігуровані тести 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:
Java
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import static org.mockito.BDDMockito.given; @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" ); } } 
Kotlin
import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org .springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server. 
            @WebFluxTest(UserVehicleController::class) class MyControllerTests(@Autowired val webClient: WebTestClient) { @MockBean lateinit var userVehicleService: UserVehicleService @Test fun testExample ("sboot")) .willReturn(VehicleDetails("Honda", "Civic")) webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange() .expectStatus() .isOk .expectBody<String>().isEqualTo("Honda Civic") } } 
Ця конфігурація підтримується лише програмами WebFlux, оскільки використання WebTestClient в імітованому веб-додатку на даний момент працює тільки у WebFlux.
Аннотація @WebFluxTest не може виявляти маршрути, зареєстровані за допомогою функціонального веб-фреймворку. Для тестування бінів RouterFunction у контексті розгляньте можливість самостійного імпорту RouterFunction за допомогою анотації @Import або за допомогою анотації @SpringBootTest .
Анотація @WebFluxTest не може виявляти кастомну конфігурацію безпеки, зареєстровану як @Bean типу SecurityWebFilterChain. Щоб включити її у свій тест, потрібно імпортувати конфігурацію, що реєструє бін, за допомогою анотації @Import або анотації @SpringBootTest.
Іноді одного написання тестів у Spring WebFlux недостатньо; Spring Boot допоможе запустити повнофункціональні наскрізні тести з використанням реального сервера.