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 验证整条装配;接口的返回结构、状态码、校验、拦截逻辑都能在测试里锁死。