本节摘要:验证 Web 层不必真的监听端口。MockMvc 在进程内构造请求对象、驱动完整的过滤器链与分发器,再对响应做断言——旅程的前六站全部真实执行,只有网络这一段被省略。本节覆盖基本用法、异常路径与安全场景的验证。
启动真实服务器再发 HTTP 请求做测试,慢且要管端口冲突。进程内模拟省掉网络往返,其余关卡照常工作:
@WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired MockMvc mvc; @MockBean OrderService orderService; // 业务层用替身,只测 Web 层 @Test void detail_returnsOrder() throws Exception { when(orderService.loadOrder(42L)).thenReturn(new OrderVO(42L, "199")); mvc.perform(get("/orders/42")) .andExpect(status().isOk()) .andExpect(jsonPath("$.amount").value("199")); } }
perform 相当于浏览器发出请求,之后请求真的穿过过滤器、分发器、参数绑定,直到控制器——旅程是真实执行的,被替换的只有业务层。断言覆盖三个层面:状态码(旅程走到哪)、响应体结构(序列化对不对)、业务值(翻译层传对没有)。
绑定失败:传错类型验证错误处理是否收口:
mvc.perform(get("/orders/abc")) .andExpect(status().isBadRequest());
安全拒绝:未认证请求应被安全链拦下,控制器根本不该执行:
@WithMockUser(username = "u1") @Test void write_needsEditorRole() throws Exception { mvc.perform(post("/api/admin/publish")) .andExpect(status().isForbidden()); // 有身份没角色 }
异步接口:第 7 章的异步返回值也能断言,先等结果再检查:
MvcResult r = mvc.perform(get("/reports")) .andExpect(request().asyncStarted()) .andReturn(); mvc.perform(asyncDispatch(r)) .andExpect(status().isOk());

⚠️ 替身注入的注解在不同测试框架版本间发生过更名,写法以所用版本的测试启动器文档为准;语义不变——把一个 Bean 换成可控的假实现。
背景:评审要求订单详情接口的测试覆盖"正常、参数非法、未认证"三条分支,用本节工具一次补齐。操作:第一步,正常路径——替身返回构造好的视图对象,断言状态码与两个关键字段。第二步,非法参数——路径传字母,断言 400,并进一步断言全局异常处理器写回的错误结构:
@Test void detail_badId_returnsStructuredError() throws Exception { mvc.perform(get("/orders/abc")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").value("参数 id 类型不正确")); }
第三步,未认证——不带身份访问受保护路径,断言 401:
@Test void detail_unauthenticated_rejected() throws Exception { mvc.perform(get("/api/orders/42")) .andExpect(status().isUnauthorized()); }
结果:三个测试全部通过,运行在五秒内。解读:三条分支恰好对应旅程的三个断点站——正常走完全程、绑定断在分发器第 3 步、认证断在过滤器链,一套进程内重放把九站地图上的三处关键落点全部钉住。变式:把第 6 章方法级注解加到控制器上,再补一个"有身份无角色"的 403 用例,安全测试矩阵就完整了;若替身漏配导致控制器抛空指针,测试会在"旅程走到哪一层"上给出精确信号——这是排测试失败时的读图方法。
其一,它验证不了真实的网络层:连接器、协议解析、压缩这些环节被绕过了,涉及超时、连接复用的行为仍需端到端环境兜底,所以 8.2 节的少量集成测试不可省。其二,替身的返回值要贴近真实形状:替身返回的字段与真实对象不一致时,序列化断言会给出误导性的失败,把它当成"契约漂移"的信号去核对真实类,比硬改测试更有价值。把重放当成日常、把端到端当成抽检,两层配合才是经济的选择。
两问收束:进程内重放覆盖了九站旅程的哪几站、省掉了哪几站;替身返回值与真实对象不一致时,失败信号出现在断言的哪一层。第二问答对,说明你已经在用"层"的视角读测试失败。
再补一个团队实践:把本节的三类分支测试做成接口的标配模板,新接口合并前必须补齐正常、绑定失败、权限拒绝三个用例,模板代码直接复制改路径即可。规范的价值在于把个体经验沉淀为流程——新人第一周写出的测试就有了骨架。