Appearance
MockMvc Setup Features:让测试配置更优雅 🎯
什么是 MockMvc Setup Features?
MockMvc Setup Features 是 Spring Test 框架中的一套配置功能,它允许我们在创建 MockMvc 实例时预设一些通用的请求和响应行为。这就像是为你的测试环境设置一套"默认规则",让每个测试都能自动遵循这些规则。
NOTE
MockMvc Setup Features 的核心理念是"约定优于配置"——通过预设常用的测试配置,减少重复代码,提高测试的一致性和可维护性。
为什么需要 Setup Features?
解决的核心痛点
在没有 Setup Features 之前,我们的测试代码可能是这样的:
kotlin
@Test
fun testGetUser() {
mockMvc.perform(
get("/api/users/1")
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
}
@Test
fun testCreateUser() {
mockMvc.perform(
post("/api/users")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content("""{"name":"张三"}""")
)
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
}
kotlin
// 在测试类初始化时配置一次
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(UserController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
}
@Test
fun testGetUser() {
// 自动应用默认配置,代码更简洁
mockMvc.perform(get("/api/users/1"))
.andExpect(jsonPath("$.name").value("张三"))
}
TIP
可以看到,使用 Setup Features 后,我们的测试代码变得更加简洁,重复的配置被消除了,测试的焦点更加集中在业务逻辑的验证上。
核心功能详解
1. 默认请求配置 (defaultRequest)
defaultRequest
允许我们为所有请求设置默认的 HTTP 头、参数等。
kotlin
@TestMethodOrder(OrderAnnotation::class)
class UserControllerTest {
private lateinit var mockMvc: MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(UserController())
.defaultRequest(
get("/")
.accept(MediaType.APPLICATION_JSON)
.header("X-Client-Version", "1.0.0")
.param("source", "test")
)
.build()
}
@Test
@Order(1)
fun `测试获取用户信息 - 自动应用默认配置`() {
// 这个请求会自动包含上面设置的默认头和参数
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").exists())
}
}
2. 总是期望的响应 (alwaysExpect)
alwaysExpect
设置所有请求都应该满足的响应条件。
kotlin
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(UserController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.alwaysExpect(header().string("X-Response-Time").exists())
.build()
}
@Test
fun `所有成功的API调用都会自动验证基本响应格式`() {
// 无需重复写 status().isOk() 和 contentType 的验证
mockMvc.perform(get("/api/users/1"))
.andExpect(jsonPath("$.id").value(1)) // 只需关注业务逻辑验证
}
WARNING
使用 alwaysExpect
时要谨慎,确保设置的期望对所有测试都适用。如果某个测试需要验证错误状态码,可能需要单独配置 MockMvc 实例。
3. 自定义配置器 (MockMvcConfigurer)
Spring 提供了 MockMvcConfigurer
接口,允许我们创建可重用的配置组件。
内置配置器:SharedHttpSession
kotlin
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(UserController())
.apply(SharedHttpSessionConfigurer.sharedHttpSession())
.build()
}
@Test
fun `测试需要会话状态的操作`() {
// 第一个请求:登录
mockMvc.perform(
post("/api/login")
.param("username", "admin")
.param("password", "123456")
)
.andExpect(status().isOk())
// 第二个请求:访问需要登录的接口
// 会话会自动保持,无需手动处理 Cookie
mockMvc.perform(get("/api/profile"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("admin"))
}
自定义配置器
我们也可以创建自己的配置器来封装常用的测试设置:
kotlin
// 自定义配置器
class ApiTestConfigurer : MockMvcConfigurer {
override fun afterConfigurerAdded(builder: ConfigurableMockMvcBuilder<*>) {
builder
.defaultRequest(
get("/")
.accept(MediaType.APPLICATION_JSON)
.header("X-API-Version", "v1")
.header("X-Client-Type", "test")
)
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
.alwaysExpect(header().string("X-Response-Time").exists())
}
}
// 使用自定义配置器
@TestMethodOrder(OrderAnnotation::class)
class ProductControllerTest {
private lateinit var mockMvc: MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(ProductController())
.apply(ApiTestConfigurer())
.build()
}
@Test
@Order(1)
fun `测试商品列表接口`() {
mockMvc.perform(get("/api/products"))
.andExpect(jsonPath("$.data").isArray())
.andExpect(jsonPath("$.total").isNumber())
}
}
实际业务场景应用
场景1:API 版本控制测试
kotlin
@TestMethodOrder(OrderAnnotation::class)
class ApiVersionTest {
private lateinit var mockMvcV1: MockMvc
private lateinit var mockMvcV2: MockMvc
@BeforeEach
fun setup() {
// V1 API 配置
mockMvcV1 = MockMvcBuilders.standaloneSetup(UserControllerV1())
.defaultRequest(
get("/")
.header("X-API-Version", "v1")
.accept(MediaType.APPLICATION_JSON)
)
.alwaysExpect(status().isOk())
.build()
// V2 API 配置
mockMvcV2 = MockMvcBuilders.standaloneSetup(UserControllerV2())
.defaultRequest(
get("/")
.header("X-API-Version", "v2")
.accept(MediaType.APPLICATION_JSON)
)
.alwaysExpect(status().isOk())
.build()
}
@Test
@Order(1)
fun `测试V1和V2接口的响应格式差异`() {
// V1 接口返回简单格式
mockMvcV1.perform(get("/api/users/1"))
.andExpect(jsonPath("$.name").exists())
.andExpect(jsonPath("$.email").exists())
// V2 接口返回增强格式
mockMvcV2.perform(get("/api/users/1"))
.andExpect(jsonPath("$.profile.name").exists())
.andExpect(jsonPath("$.profile.email").exists())
.andExpect(jsonPath("$.metadata.version").value("v2"))
}
}
场景2:多租户系统测试
kotlin
class MultiTenantConfigurer(private val tenantId: String) : MockMvcConfigurer {
override fun afterConfigurerAdded(builder: ConfigurableMockMvcBuilder<*>) {
builder.defaultRequest(
get("/")
.header("X-Tenant-ID", tenantId)
.accept(MediaType.APPLICATION_JSON)
)
}
}
@TestMethodOrder(OrderAnnotation::class)
class MultiTenantTest {
private lateinit var tenantAMockMvc: MockMvc
private lateinit var tenantBMockMvc: MockMvc
@BeforeEach
fun setup() {
tenantAMockMvc = MockMvcBuilders.standaloneSetup(OrderController())
.apply(MultiTenantConfigurer("tenant-a"))
.build()
tenantBMockMvc = MockMvcBuilders.standaloneSetup(OrderController())
.apply(MultiTenantConfigurer("tenant-b"))
.build()
}
@Test
@Order(1)
fun `测试不同租户的数据隔离`() {
// 租户A的订单
tenantAMockMvc.perform(get("/api/orders"))
.andExpect(jsonPath("$.data[0].tenantId").value("tenant-a"))
// 租户B的订单
tenantBMockMvc.perform(get("/api/orders"))
.andExpect(jsonPath("$.data[0].tenantId").value("tenant-b"))
}
}
测试流程可视化
最佳实践建议
1. 合理使用 alwaysExpect
注意事项
不要在 alwaysExpect
中设置过于具体的期望,这可能会限制测试的灵活性。
kotlin
// ❌ 不推荐:过于具体
.alwaysExpect(jsonPath("$.code").value(200))
.alwaysExpect(jsonPath("$.message").value("success"))
// ✅ 推荐:通用的格式验证
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
2. 创建可重用的配置器
kotlin
// 为不同类型的测试创建专门的配置器
object TestConfigurers {
fun apiTest() = ApiTestConfigurer()
fun adminTest() = AdminTestConfigurer()
fun mobileTest() = MobileTestConfigurer()
}
// 使用时更加语义化
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.apply(TestConfigurers.apiTest())
.build()
3. 环境特定配置
kotlin
@TestProfile("integration")
class IntegrationTestConfigurer : MockMvcConfigurer {
override fun afterConfigurerAdded(builder: ConfigurableMockMvcBuilder<*>) {
builder
.defaultRequest(
get("/")
.header("X-Environment", "integration")
.header("X-Trace-Enabled", "true")
)
}
}
总结
MockMvc Setup Features 通过提供统一的配置机制,让我们能够:
- 减少重复代码 📝:避免在每个测试中重复设置相同的请求头和响应期望
- 提高一致性 🎯:确保所有测试都遵循相同的基础规则
- 增强可维护性 🔧:配置集中管理,修改时只需要改一个地方
- 支持复杂场景 🚀:通过自定义配置器支持多租户、版本控制等复杂测试场景
TIP
Setup Features 的核心价值在于让测试代码更加专注于业务逻辑的验证,而不是被技术细节所干扰。这正体现了"测试即文档"的理念——好的测试应该清晰地表达业务意图。
通过合理使用这些功能,我们可以构建出既简洁又强大的测试套件,为代码质量保驾护航! ✅