Skip to content

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 中,你可以配置自动导入建议:

  1. 打开 SettingsEditorGeneralAuto Import
  2. 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 在很多场景下仍然是最佳选择,特别是在需要精细控制测试环境的情况下。