slice
Spring Boot 引入了一段时间的测试切片 ,花了我一些时间来研究它并探索它的一些细微差别。
背景
使用此功能的主要原因是减少样板。 考虑一个看起来像这样的控制器,仅适用于使用Kotlin编写的各种控制器。
@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测试将遵循以下原则:
@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
}
}
}
设置这样的测试涉及很多仪式-理解Web环境的Web应用程序上下文被引入,需要创建设置Spring MVC环境的配置,以及满足测试框架需求的MockMvc在每次测试之前进行设置。
网页切片测试
与以前的测试相比,Web Slice测试要简单得多,它专注于测试控制器并隐藏了许多样板代码:
@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 Application上下文但过滤掉与Web层无关的所有内容并仅加载已传递到@WebTest批注中的控制器来工作。 控制器需要的任何依赖关系都可以作为模拟注入。
涉及到一些细微差别,例如,如果我想自己注入某个字段,则可以使用自定义的Spring Configuration进行测试,对于测试,可以使用内部@TestConfiguration注释的静态类来完成。以下方式:
@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”的Bean将添加到Slice测试所依赖的配置中,而不是完全替换它。
另一方面,如果我想重写带注释的主“ @SpringBootApplication”主类的加载,则可以显式传递一个Spring Configuration类,但要注意的是,我现在必须负责所有相关的加载。 Spring Boot具有我自己的功能(启用自动配置,适当的扫描等),因此可以通过以下方法来围绕它显式地将配置注释为Spring Boot应用程序:
@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)
}
}
}
但是要注意的是,现在其他测试可能最终会找到这种不理想的内部配置!因此,我的学习一直依赖于最低限度的切片测试,并在需要时使用@TestConfiguration对其进行扩展。
我在github仓库中有一些更详细的代码示例,其中包含一些可用的示例。
翻译自: https://www.javacodegeeks.com/2017/06/spring-boot-web-slice-test-sample.html
slice