Appearance
MockMvc 静态导入:让测试代码更优雅 🚀
为什么需要静态导入?
在 Spring Boot 的 Web 层测试中,MockMvc 是我们最常用的测试工具之一。但是,如果不使用静态导入,我们的测试代码会变得冗长且难以阅读。
NOTE
静态导入(Static Import)是 Java 5 引入的特性,允许我们直接使用类的静态方法和字段,而无需使用类名前缀。
问题场景:没有静态导入的痛苦 😵
让我们先看看不使用静态导入时的测试代码:
kotlin
@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `should return user when valid id provided`() {
// 没有静态导入的写法 - 冗长且难读
mockMvc.perform(
MockMvcRequestBuilders.get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("张三"))
.andDo(MockMvcResultHandlers.print())
}
}
kotlin
// 静态导入
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.*
@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `should return user when valid id provided`() {
// 使用静态导入后的写法 - 简洁优雅
mockMvc.perform(
get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("张三"))
.andDo(print())
}
}
MockMvc 核心静态导入类详解 📚
MockMvc 测试中需要静态导入的四个核心类:
1. MockMvcBuilders.* - 构建器之王 🏗️
用于创建和配置 MockMvc 实例:
kotlin
import org.springframework.test.web.servlet.setup.MockMvcBuilders.*
@Test
fun `test with standalone setup`() {
val mockMvc = standaloneSetup(UserController())
.setControllerAdvice(GlobalExceptionHandler())
.build()
// 执行测试...
}
TIP
standaloneSetup()
适用于单元测试,webAppContextSetup()
适用于集成测试。
2. MockMvcRequestBuilders.* - 请求构造专家 📨
用于构建各种 HTTP 请求:
kotlin
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
@Test
fun `test different HTTP methods`() {
// GET 请求
mockMvc.perform(get("/api/users/{id}", 1))
// POST 请求
mockMvc.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""{"name":"李四","email":"[email protected]"}""")
)
// PUT 请求
mockMvc.perform(
put("/api/users/{id}", 1)
.contentType(MediaType.APPLICATION_JSON)
.content("""{"name":"王五","email":"[email protected]"}""")
)
// DELETE 请求
mockMvc.perform(delete("/api/users/{id}", 1))
}
3. MockMvcResultMatchers.* - 断言验证大师 ✅
用于验证响应结果:
kotlin
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
@Test
fun `test response validation`() {
mockMvc.perform(get("/api/users/1"))
// 状态码验证
.andExpect(status().isOk())
.andExpect(status().is(200))
// 内容类型验证
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
// JSON 路径验证
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("张三"))
.andExpect(jsonPath("$.email").value("[email protected]"))
// 头部验证
.andExpect(header().string("Content-Type", "application/json"))
}
4. MockMvcResultHandlers.* - 调试助手 🔍
用于处理和打印测试结果:
kotlin
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.*
@Test
fun `test with result handlers`() {
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andDo(print()) // [!code highlight] // 打印完整的请求和响应信息
.andDo(log()) // [!code highlight] // 记录到日志
}
完整的测试示例 🎯
让我们通过一个完整的用户管理 API 测试来看看静态导入的威力:
完整的 UserController 测试示例
kotlin
package com.example.controller
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.*
import org.mockito.BDDMockito.*
import com.example.service.UserService
import com.example.model.User
@WebMvcTest(UserController::class)
class UserControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@MockBean
private lateinit var userService: UserService
@Autowired
private lateinit var objectMapper: ObjectMapper
@Test
fun `should return user when valid id provided`() {
// Given - 准备测试数据
val user = User(1L, "张三", "[email protected]")
given(userService.findById(1L)).willReturn(user)
// When & Then - 执行测试并验证结果
mockMvc.perform(
get("/api/users/{id}", 1L)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("张三"))
.andExpect(jsonPath("$.email").value("[email protected]"))
.andDo(print())
}
@Test
fun `should create user when valid data provided`() {
// Given
val newUser = User(null, "李四", "[email protected]")
val savedUser = User(2L, "李四", "[email protected]")
given(userService.save(any(User::class.java))).willReturn(savedUser)
// When & Then
mockMvc.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(newUser))
)
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(2))
.andExpect(jsonPath("$.name").value("李四"))
.andDo(print())
}
@Test
fun `should return 404 when user not found`() {
// Given
given(userService.findById(999L)).willThrow(UserNotFoundException("User not found"))
// When & Then
mockMvc.perform(
get("/api/users/{id}", 999L)
)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("User not found"))
.andDo(print())
}
@Test
fun `should validate user input when creating user`() {
// Given - 无效的用户数据(缺少邮箱)
val invalidUser = """{"name":""}"""
// When & Then
mockMvc.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUser)
)
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").isArray)
.andDo(print())
}
}
IDE 配置技巧 💡
IntelliJ IDEA 配置
在 IntelliJ IDEA 中,你可以配置自动导入建议:
- 打开
Settings
→Editor
→General
→Auto Import
- 在
Kotlin
部分,添加以下包到Exclude from import and completion
:org.springframework.test.web.servlet.request.MockMvcRequestBuilders
org.springframework.test.web.servlet.result.MockMvcResultMatchers
org.springframework.test.web.servlet.result.MockMvcResultHandlers
org.springframework.test.web.servlet.setup.MockMvcBuilders
TIP
记住搜索 MockMvc*
的技巧!这样可以快速找到所有相关的类。
WebTestClient vs MockMvc 🆚
Spring 5 引入了 WebTestClient
,它提供了更现代的测试体验:
kotlin
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerWebTestClientTest {
@Autowired
private lateinit var webTestClient: WebTestClient
@Test
fun `should return user with WebTestClient`() {
webTestClient.get()
.uri("/api/users/{id}", 1)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.name").isEqualTo("张三")
}
}
kotlin
@WebMvcTest(UserController::class)
class UserControllerMockMvcTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `should return user with MockMvc`() {
mockMvc.perform(
get("/api/users/{id}", 1)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "application/json"))
.andExpect(jsonPath("$.name").value("张三"))
}
}
最佳实践建议 🌟
1. 创建测试基类
kotlin
abstract class BaseControllerTest {
companion object {
// 集中管理静态导入,避免每个测试类都重复导入
@JvmStatic
protected fun mockMvcGet(url: String) = get(url)
@JvmStatic
protected fun mockMvcPost(url: String) = post(url)
// ... 其他常用方法
}
}
2. 使用测试工具类
kotlin
object TestUtils {
fun createJsonContent(obj: Any): String {
return ObjectMapper().writeValueAsString(obj)
}
fun performGetRequest(mockMvc: MockMvc, url: String, vararg params: Any) =
mockMvc.perform(
get(url, *params)
.accept(MediaType.APPLICATION_JSON)
)
}
3. 组织导入语句
kotlin
// 标准库导入
import org.junit.jupiter.api.Test
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
// MockMvc 静态导入 - 集中在一起
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.*
import org.springframework.test.web.servlet.setup.MockMvcBuilders.*
// 应用相关导入
import com.example.controller.UserController
总结 📝
IMPORTANT
MockMvc 静态导入的核心价值在于让测试代码更加简洁、可读,提高开发效率。
记住这四个关键类:
MockMvcBuilders.*
- 构建 MockMvc 实例MockMvcRequestBuilders.*
- 构建 HTTP 请求MockMvcResultMatchers.*
- 验证响应结果MockMvcResultHandlers.*
- 处理测试结果
快速记忆法: 搜索 MockMvc*
就能找到所有相关类! 🎉
通过合理使用静态导入,我们的测试代码变得更加优雅和专业。虽然 WebTestClient
提供了更现代的替代方案,但 MockMvc 在很多场景下仍然是最佳选择,特别是在需要精细控制测试环境的情况下。