Біни-імітації та біни-шпигуни
При виконанні тестів іноді необхідно імітувати певні компоненти в контексті програми. Наприклад, у тебе може бути фасад певної віддаленої служби, яка недоступна під час розробки. Імітування також може бути корисним, якщо необхідно імітувати збої, які важко викликати в реальному оточенні.
Spring Boot містить анотацію @MockBean
, яку можна використовувати для визначення Mockito-імітації для
біна всередині ApplicationContext
.
Можна використовувати анотацію для додавання нових бінів або заміни одного існуючого визначення біну. Анотацію
можна використовувати безпосередньо для тестових класів, для полів усередині тесту або для класів та полів з
анотацією @Configuration
. При використанні з полем екземпляр створеного об'єкта-імітації також
впроваджується. Біни-імітації автоматично скидаються після виконання кожного тестового методу.
Якщо тест використовує одну з тестових анотацій Spring Boot (наприклад,
@ SpringBootTest
), ця функція автоматично активується. Щоб використовувати цю функцію з іншим типом
організації, необхідно явно додати слухачі, як показано в наступному прикладі:
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 {
// ...
}
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 {
// ...
}
У наступному прикладі існуючий бін RemoteService
замінюється імітаційною реалізацією:
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");
}
}
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 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
з JacksonGson
Jsonb
@JsonTest
можна
знайти в додатку до документації.
Якщо необхідно конфігурувати елементи автоконфігурації, можна використовувати анотацію @AutoConfigureJsonTesters
.
Spring Boot містить допоміжні класи на основі AssertJ, які працюють з бібліотеками JSONAssert та JsonPath,
призначені для перевірки коректного передбачуваного відображення JSON. Класи JacksonTester
, GsonTester
,
JsonbTester
та BasicJsonTester
можна використовувати для бібліотек Jackson, Gson, Jsonb та
Strings відповідно. Будь-які допоміжні поля тестового класу можуть бути позначені анотацією @Autowired
під час використання анотації @JsonTest
. У цьому прикладі показаний тестовий клас для бібліотеки
Jackson:
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");
}
}
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 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
:
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"));
}
}
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.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@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:
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");
}
}
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.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
@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 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
:
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");
}
}
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.expectBody
@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
.
Іноді одного написання тестів у Spring WebFlux недостатньо; Spring Boot допоможе запустити повнофункціональні наскрізні тести з використанням реального сервера.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ