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 实例,因此可以直接设置 contentTypecontentaccept 属性。

然后通过 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


原始标题:MockMvc Kotlin DSL