Spring Boot представила тест нарезки некоторое время назад, и мне потребовалось некоторое время, чтобы разобраться с ним и изучить некоторые его нюансы.
Фон
Основной причиной использования этой функции является уменьшение шаблона. Рассмотрим контроллер, который выглядит так, только для разнообразия, написанного с использованием Kotlin .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
@RestController @RequestMapping ( "/users" ) class UserController( private val userRepository: UserRepository, private val userResourceAssembler: UserResourceAssembler) { @GetMapping fun getUsers(pageable: Pageable, pagedResourcesAssembler: PagedResourcesAssembler<User>): PagedResources<Resource<User>> { val users = userRepository.findAll(pageable) return pagedResourcesAssembler.toResource(users, this .userResourceAssembler) } @GetMapping ( "/{id}" ) fun getUser(id: Long): Resource<User> { return Resource(userRepository.findOne(id)) } } |
Традиционный тест Spring Mock MVC для тестирования этого контроллера будет выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
@RunWith (SpringRunner:: class ) @WebAppConfiguration @ContextConfiguration class UserControllerTests { lateinit var mockMvc: MockMvc @Autowired private val wac: WebApplicationContext? = null @Before fun setup() { this .mockMvc = MockMvcBuilders.webAppContextSetup( this .wac).build() } @Test fun testGetUsers() { this .mockMvc.perform(get( "/users" ) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk) } @EnableSpringDataWebSupport @EnableWebMvc @Configuration class SpringConfig { @Bean fun userController(): UserController { return UserController(userRepository(), UserResourceAssembler()) } @Bean fun userRepository(): UserRepository { val userRepository = Mockito.mock(UserRepository:: class .java) given(userRepository.findAll(Matchers.any(Pageable:: class .java))) .willAnswer({ invocation -> val pageable = invocation.arguments[ 0 ] as Pageable PageImpl( listOf( , pageable, 10 ) }) return userRepository } } } |
Создание такого теста требует много церемоний — контекст веб-приложения, который понимает, что веб-среда задействована, необходимо создать конфигурацию, которая настраивает среду Spring MVC, и MockMvc, который отвечает требованиям инфраструктуры тестирования. настраиваться перед каждым испытанием.
Тест веб-слайса
Тестирование веб-фрагмента по сравнению с предыдущим тестом намного проще и фокусируется на тестировании контроллера и скрывает много стандартного кода:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@RunWith (SpringRunner:: class ) @WebMvcTest (UserController:: class ) class UserControllerSliceTests { @Autowired lateinit var mockMvc: MockMvc @MockBean lateinit var userRepository: UserRepository @SpyBean lateinit var userResourceAssembler: UserResourceAssembler @Test fun testGetUsers() { this .mockMvc.perform(get( "/users" ).param( "page" , "0" ).param( "size" , "1" ) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk) } @Before fun setUp(): Unit { given(userRepository.findAll(Matchers.any(Pageable:: class .java))) .willAnswer({ invocation -> val pageable = invocation.arguments[ 0 ] as Pageable PageImpl( listOf( , pageable, 10 ) }) } } |
Он работает, создавая контекст приложения Spring, но отфильтровывая все, что не относится к веб-слою, и загружая только контроллер, который был передан в аннотацию @WebTest. Любая зависимость, которая требуется контроллеру, может быть введена в качестве макета.
Говоря о некоторых нюансах, скажем, если бы я хотел внедрить одно из полей самостоятельно, нужно сделать так, чтобы тест использовал пользовательскую конфигурацию Spring, для теста это делается с использованием внутреннего статического класса, аннотированного @TestConfiguration, следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
@RunWith (SpringRunner:: class ) @WebMvcTest (UserController:: class ) class UserControllerSliceTests { @Autowired lateinit var mockMvc: MockMvc @Autowired lateinit var userRepository: UserRepository @Autowired lateinit var userResourceAssembler: UserResourceAssembler @Test fun testGetUsers() { this .mockMvc.perform(get( "/users" ).param( "page" , "0" ).param( "size" , "1" ) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk) } @Before fun setUp(): Unit { given(userRepository.findAll(Matchers.any(Pageable:: class .java))) .willAnswer({ invocation -> val pageable = invocation.arguments[ 0 ] as Pageable PageImpl( listOf( , pageable, 10 ) }) } @TestConfiguration class SpringConfig { @Bean fun userResourceAssembler(): UserResourceAssembler { return UserResourceAssembler() } @Bean fun userRepository(): UserRepository { return mock(UserRepository:: class .java) } } } |
Компоненты из «TestConfiguration» добавляют к конфигурации, от которой зависят тесты Slice, и не заменяют ее полностью.
С другой стороны, если я хочу переопределить загрузку основного аннотированного класса «@SpringBootApplication», тогда я могу явно передать класс Spring Configuration, но суть в том, что теперь мне нужно позаботиться обо всей загрузке соответствующей Spring Boot поддерживает себя (включает автоконфигурацию, соответствующее сканирование и т. Д.), Поэтому способ явно аннотировать конфигурацию как Spring Boot Application следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
@RunWith (SpringRunner:: class ) @WebMvcTest (UserController:: class ) class UserControllerExplicitConfigTests { @Autowired lateinit var mockMvc: MockMvc @Autowired lateinit var userRepository: UserRepository @Test fun testGetUsers() { this .mockMvc.perform(get( "/users" ).param( "page" , "0" ).param( "size" , "1" ) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk) } @Before fun setUp(): Unit { given(userRepository.findAll(Matchers.any(Pageable:: class .java))) .willAnswer({ invocation -> val pageable = invocation.arguments[ 0 ] as Pageable PageImpl( listOf( , pageable, 10 ) }) } @SpringBootApplication (scanBasePackageClasses = arrayOf(UserController:: class )) @EnableSpringDataWebSupport class SpringConfig { @Bean fun userResourceAssembler(): UserResourceAssembler { return UserResourceAssembler() } @Bean fun userRepository(): UserRepository { return mock(UserRepository:: class .java) } } } |
Однако подвох состоит в том, что теперь другие тесты могут в конечном итоге найти эту внутреннюю конфигурацию, которая далека от идеальной!
У меня есть немного более подробный пример кода, доступный в моем репозитории github, в котором есть рабочие примеры, с которыми можно поиграть.
Ссылка: | Тест Spring Boot Web Slice — образец от нашего партнера JCG Биджу Кунджуммена в блоге all and sundry. |