1. 概述
在本篇文章中,我们将快速了解 Spring Framework 5.2+ 中新增的、专为 Kotlin 设计的 MockMvc 支持功能。
⚠️ 注意:由于 Spring Framework 5.2 尚未正式发布(GA),我们需要使用 Spring Milestone 仓库 来获取相关依赖。
2. 待测试的 Controller
我们先来搭建一个用于测试的 Controller 示例。
定义如下数据类:
// Payload
data class Name(val first: String, val last: String)
// Web request
data class Request(val name: Name)
// Web response
@JsonInclude(JsonInclude.Include.NON_NULL) data class Response(val error: String?)
接着是一个简单的 REST Controller,它会对传入的请求体进行校验:
@RestController
@RequestMapping("/mockmvc")
class MockMvcController {
@RequestMapping(value = ["/validate"], method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE])
fun validate(@RequestBody request: Request): Response {
val error = if (request.name.first == "admin") {
null
} else {
ERROR
}
return Response(error)
}
companion object {
const val ERROR = "invalid user"
}
}
这个 Controller 提供了一个 POST 接口 /mockmvc/validate
,接收一个自定义的 Request 对象作为 JSON 输入,并返回一个 Response 对象的 JSON 响应。
3. 经典测试方式
我们可以使用传统的 MockMvc 方式来测试上面的 Controller:
mockMvc.perform(MockMvcRequestBuilders
.post("/mockmvc/validate")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(Request(Name("admin", "")))))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.content().string("{}"))
这段代码做了以下几件事:
- ✅ 验证 HTTP 状态码是 200
- ✅ 验证响应的内容类型是 application/json
- ✅ 验证响应内容为空 JSON 对象
{}
整体看起来已经很清晰了,但如果你追求更简洁、更具表现力的写法,可以试试 Kotlin DSL。
4. 现代化测试方式(Kotlin DSL)
同样的测试逻辑可以用 Kotlin DSL 写得更优雅:
mockMvc.post("/mockmvc/validate") {
contentType = MediaType.APPLICATION_JSON
content = mapper.writeValueAsString(Request(Name("admin", "")))
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
content { json("{}") }
}
实现原理
要启用这一特性,需要使用 Spring Framework 5.2+,其中包含了 MockMvcExtensions.kt
这个模块 —— 它是专门为 MockMvc 提供的 Kotlin DSL 扩展。
首先调用的是扩展函数 MockMvc.post()
,其参数是一个 MockHttpServletRequestDsl
的 lambda:
mockMvc.post("/mockmvc/validate") {
// 这里是扩展方法体
}
在这个 lambda 中,this
指向的是 MockHttpServletRequestDsl
实例,因此可以直接设置 contentType
、content
和 accept
属性。
然后通过 andExpect
方法传入另一个 DSL 块:
andExpect {
// Extension method body
}
这会构造出完整的断言逻辑,语义更明确,结构也更清晰。
5. 进一步优化测试代码
如果需要编写多个测试用例,建议将公共部分抽取出来复用。
比如,我们可以封装一个通用的测试方法:
private fun doTest(input: Request, expectation: Response) {
mockMvc.post("/mockmvc/validate") {
contentType = MediaType.APPLICATION_JSON
content = mapper.writeValueAsString(input)
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
content { json(mapper.writeValueAsString(expectation)) }
}
}
这样具体的测试方法就变得非常简洁:
@Test
fun `when supported user is given then validation is successful`() {
doTest(Request(Name("admin", "")), Response(null))
}
@Test
fun `when unsupported user is given then validation is failed`() {
doTest(Request(Name("some-name", "some-surname")), Response(MockMvcController.ERROR))
}
这种方式不仅减少了重复代码,也让测试意图更加明显。
6. 总结
本文展示了如何使用 Spring Framework 5.2 新增的 MockMvc Kotlin DSL 来简化测试代码的编写。虽然它不是万能的银弹,但在 Kotlin 项目中确实能带来更高的可读性和表达力。
此外,这也是一个很好的例子,说明如何利用 Kotlin 的 DSL 特性提升代码质量。如果你正在使用 Kotlin 开发 Spring 应用,不妨尝试一下这种风格。
一如既往,完整源码可以在 GitHub 上找到:Baeldung/kotlin-tutorials/spring-mvc-kotlin。