Статьи

Тест Spring Boot Web Slice — Образец

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(
                                        User(id = 1, fullName = "one", password = "one", email = "one@one.com"),
                                        User(id = 2, fullName = "two", password = "two", email = "two@two.com"))
                                , 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(
                                    User(id = 1, fullName = "one", password = "one", email = "one@one.com"),
                                    User(id = 2, fullName = "two", password = "two", email = "two@two.com"))
                            , 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(
                                    User(id = 1, fullName = "one", password = "one", email = "one@one.com"),
                                    User(id = 2, fullName = "two", password = "two", email = "two@two.com"))
                            , 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(
                                    User(id = 1, fullName = "one", password = "one", email = "one@one.com"),
                                    User(id = 2, fullName = "two", password = "two", email = "two@two.com"))
                            , 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.