MockMvc 测试:接口测试基本写法

不启动 Tomcat 也能测接口?MockMvc(spring-test 提供)用 MockHttpServletRequest/Response 模拟一次完整的 MVC 调用:请求真正走 DispatcherServlet、HandlerMapping、参数解析、序列化,只是不经过真实网络与容器。它是 Spring MVC 单元/集成测试的主力。

依赖与静态导入

pom 引入 spring-test(scope=test,版本与 spring-webmvc 一致),测试类静态导入请求构造与结果匹配器:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

方式一:standaloneSetup 轻量启动

只装配被测控制器,快、隔离好,适合控制器单元测试:

class UserApiTest {
    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(new UserApi(new UserService()))
                .build();
    }

    @Test
    void getUser_returnsJson() throws Exception {
        mockMvc.perform(get("/api/users/{id}", 1L))           // 发 GET
                .andExpect(status().isOk())                   // 状态码 200
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.name").value("张三"));  // 断言 JSON 字段
    }
}

jsonPath 断言需额外引入 JSONPath 库(com.jayway.jsonpath:json-path);不引入时可用 content().string(containsString("张三")) 做粗断言。

方式二:webAppContextSetup 全量集成

把完整 Spring 配置加载进来,走真实的 Controller → Service → 数据层(配合测试库),验证的是整条链:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, WebConfig.class})
class UserApiIntegrationTest {
    @Autowired
    private WebApplicationContext context;
    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }
    // 测试方法同方式一
}

常用请求构造

// POST JSON 体
mockMvc.perform(post("/api/users")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"name\":\"张三\",\"age\":20}"))
        .andExpect(status().isCreated());

// 查询参数 / 表单参数 / 请求头
mockMvc.perform(get("/api/search")
        .param("keyword", "spring")
        .param("page", "2")
        .header("X-Trace-Id", "abc"))
        .andExpect(status().isOk());

// 文件上传:用 MockMultipartFile
mockMvc.perform(multipart("/api/upload")
        .file(new MockMultipartFile("file", "a.txt", "text/plain", "hello".getBytes())))
        .andExpect(status().isOk());

常用结果断言

断言含义
status().isOk() / isNotFound() / is4xxClientError()状态码
content().json("{"code":0}")整体 JSON 比对
jsonPath("$.data.id").value(1)JSON 字段取值
header().string("Location", containsString("/api/users/"))响应头
redirectedUrl("/login")重定向目标(配合拦截器测试)
model().attributeExists("users")视图模型里有该属性

辅助方法:.andDo(print()) 打印完整请求响应便于排错;.andReturn() 拿到 MvcResult 再取响应体做复杂断言。

验证异常与安全相关场景

  • 参数校验:POST 缺字段的 JSON → andExpect(status().isBadRequest()),再断言统一错误体。
  • 登录拦截:不携带会话访问受保护路径 → 期望 302 到 /login;携带会话(.session(mockSession))→ 200。

常见坑

  • standaloneSetup 不会加载 @ControllerAdvice 等全局 Bean,要测全局异常/拦截器要么手动 .setControllerAdvice(...)、.addInterceptors(...),要么改用 webAppContextSetup。
  • MockMvc 测不了真实 Servlet 容器行为(真实 WebSocket、真实上传大文件),这类场景用 Testcontainers/真容器冒烟。
  • Spring Boot 场景有 @WebMvcTest/@AutoConfigureMockMvc 简化装配,属 Boot 内容,本篇只讲原生 Spring MVC 写法。

MockMvc = 请求构造器(perform)+ 结果匹配器(andExpect)。先 standaloneSetup 快速验证控制器逻辑,再 webAppContextSetup 验证整条装配;接口的返回结构、状态码、校验、拦截逻辑都能在测试里锁死。

笔记加载中…