当前位置: 首页 > news >正文

Mockito 单测入门

Mockito 单测入门

Spring Boot 项目中最精简的 Mockito 示例 — Service / 三方依赖 / Controller


1 被测代码准备

以下是一个简单的聊天消息服务,内含需要测试的三种典型场景。

@ServicepublicclassChatMsgService{@AutowiredprivateChatMsgRepositoryrepo;// Spring 注入@AutowiredprivateSmsClientsmsClient;// 三方依赖(如阿里云短信 SDK)publicChatMsgsend(Integeruid,Stringmessage){ChatMsgmsg=newChatMsg();msg.setUid(uid);msg.setMessage(message);msg.setCreateTime(LocalDateTime.now());returnrepo.save(msg);// 调用 Spring Bean}publicStringsendVerifyCode(Stringphone){Stringcode="123456";smsClient.send(phone,code);// 调用三方 SDKreturncode;}}
@RestController@RequestMapping("/chat")publicclassChatMsgController{@AutowiredprivateChatMsgServiceservice;@PostMapping("/send")publicChatMsgsend(@RequestParamIntegeruid,@RequestParamStringmessage){returnservice.send(uid,message);}}

2 依赖配置

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>

spring-boot-starter-test已包含 Mockito、MockMvc、JUnit 5,无需额外引入。


3 Service 单测 — @InjectMocks + @Mock

核心:@Mock创建 Mock 对象,@InjectMocks自动注入到被测类。

@ExtendWith(MockitoExtension.class)// 启用 MockitoclassChatMsgServiceTest{@MockprivateChatMsgRepositoryrepo;// 模拟 Spring Bean@MockprivateSmsClientsmsClient;// 模拟三方依赖@InjectMocksprivateChatMsgServiceservice;// 自动注入上面两个 mock@TestvoidtestSend(){// 准备ChatMsgsaved=newChatMsg();saved.setId(1L);saved.setUid(100);saved.setMessage("hello");Mockito.when(repo.save(Mockito.any())).thenReturn(saved);// 执行ChatMsgresult=service.send(100,"hello");// 验证Assertions.assertEquals(1L,result.getId());Mockito.verify(repo,Mockito.times(1)).save(Mockito.any());}@TestvoidtestSendVerifyCode(){// 执行Stringcode=service.sendVerifyCode("13800138000");// 验证 — 不关心三方 SDK 内部实现,只验证它被调用了Assertions.assertEquals("123456",code);Mockito.verify(smsClient,Mockito.times(1)).send("13800138000","123456");}}

为什么三方依赖这么测?短信 SDK 真实发送会扣费且依赖网络。
@Mock让它"什么都不做",我们只验证 service 正确调用了它并返回了预期结果。


4 Controller 单测 — @WebMvcTest + MockMvc

只加载 Web 层,Service 用@MockBean注入 Mock。

@WebMvcTest(ChatMsgController.class)// 只启动 Controller 层classChatMsgControllerTest{@AutowiredprivateMockMvcmockMvc;// HTTP 模拟客户端@MockBeanprivateChatMsgServiceservice;// 模拟 Service@TestvoidtestSend()throwsException{// 准备ChatMsgmockResult=newChatMsg();mockResult.setId(1L);mockResult.setUid(100);mockResult.setMessage("hello");Mockito.when(service.send(100,"hello")).thenReturn(mockResult);// 执行 & 验证 — 模拟 HTTP 请求,断言响应mockMvc.perform(MockMvcRequestBuilders.post("/chat/send").param("uid","100").param("message","hello")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1)).andExpect(MockMvcResultMatchers.jsonPath("$.message").value("hello"));}}

5 Controller POST JSON 参数测试

当接口接收 JSON 请求体时,使用@RequestBody接收参数,测试时通过.contentType(MediaType.APPLICATION_JSON)+.content()传入 JSON 字符串。

@PostMapping("/sendJson")publicChatMsgsendJson(@RequestBodyChatMsgRequestreq){returnservice.send(req.getUid(),req.getMessage());}// 配合的 DTOpublicclassChatMsgRequest{privateIntegeruid;privateStringmessage;publicIntegergetUid(){returnuid;}publicvoidsetUid(Integeruid){this.uid=uid;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}}
@TestvoidtestSendJson()throwsException{// 准备ChatMsgmockResult=newChatMsg();mockResult.setId(1L);mockResult.setUid(100);mockResult.setMessage("hello");Mockito.when(service.send(100,"hello")).thenReturn(mockResult);// JSON 请求体StringjsonBody="{\"uid\":100,\"message\":\"hello\"}";// 执行 & 验证mockMvc.perform(MockMvcRequestBuilders.post("/chat/sendJson").contentType(MediaType.APPLICATION_JSON).content(jsonBody)).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));}

关键点:.contentType(MediaType.APPLICATION_JSON)告诉 Spring 请求体是 JSON 格式;.content(jsonBody)传入 JSON 字符串。注意 JSON 中的引号需要转义。


6 Controller Header 传参测试

接口需要从 Header 中获取参数(如 token、traceId)时,使用@RequestHeader注入,测试时通过.header("key", "value")传入。

@PostMapping("/sendWithToken")publicChatMsgsendWithToken(@RequestHeader("token")Stringtoken,@RequestParamIntegeruid,@RequestParamStringmessage){// token 可用于鉴权,这里省略校验逻辑returnservice.send(uid,message);}
@TestvoidtestSendWithToken()throwsException{// 准备ChatMsgmockResult=newChatMsg();mockResult.setId(1L);mockResult.setUid(100);mockResult.setMessage("hello");Mockito.when(service.send(100,"hello")).thenReturn(mockResult);// 执行 & 验证 — 通过 .header() 传入请求头mockMvc.perform(MockMvcRequestBuilders.post("/chat/sendWithToken").header("token","abc123").param("uid","100").param("message","hello")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));}

关键点:.header("token", "abc123")模拟 HTTP 请求头。可调用多次传入多个 header。
如果有多个同名 header 需要传多个值,使用.header("key", "value1", "value2").header("key", new String[]{"v1","v2"})


7 常用 Mockito API 速查

// ---- 打桩 ----when(foo.bar()).thenReturn(xxx);// 返回固定值when(foo.bar()).thenThrow(newRuntimeException());// 抛异常when(foo.bar(anyInt())).thenAnswer(inv->42);// 动态返回// ---- 验证 ----verify(foo).bar();// 是否调用过verify(foo,times(2)).bar();// 调用次数verify(foo,never()).bar();// 从未调用verifyNoInteractions(foo);// 无任何交互// ---- 匹配器 ----any()anyInt()anyString()anyList()anyLong()anyBoolean()any(LocalDateTime.class)

8 静态方法 Mock — mockStatic

静态方法(如工具类)用Mockito.mockStatic(),需要在 try-with-resources 块内使用,mock 作用域仅限该块。

@ServicepublicclassChatMsgService{publicChatMsgsendWithMd5(Integeruid,Stringmessage){Stringmd5=DigestUtils.md5DigestAsHex(// 静态方法调用message.getBytes());ChatMsgmsg=newChatMsg();msg.setUid(uid);msg.setMessage(message+"_"+md5);returnrepo.save(msg);}}
@TestvoidtestSendWithMd5(){// try-with-resources 包裹,超出代码块自动失效try(MockedStatic<DigestUtils>mocked=Mockito.mockStatic(DigestUtils.class)){// 打桩:当调用静态方法时返回固定值mocked.when(()->DigestUtils.md5DigestAsHex(Mockito.any())).thenReturn("fake_md5");// 执行ChatMsgresult=service.sendWithMd5(100,"hello");// 验证静态方法被调用mocked.verify(()->DigestUtils.md5DigestAsHex(Mockito.any()),Mockito.times(1));Assertions.assertTrue(result.getMessage().contains("fake_md5"));}// 退出 try 块后,静态 mock 自动失效,不影响其他测试}// ====== 异常模拟 ======@TestvoidtestRepoThrows(){// 模拟 Spring Bean 抛异常Mockito.when(repo.save(Mockito.any())).thenThrow(newRuntimeException("DB down"));Assertions.assertThrows(RuntimeException.class,()->service.send(100,"hello"));}@TestvoidtestStaticMethodThrows(){try(MockedStatic<DigestUtils>mocked=Mockito.mockStatic(DigestUtils.class)){// 模拟静态方法抛异常mocked.when(()->DigestUtils.md5DigestAsHex(Mockito.any())).thenThrow(newIllegalArgumentException("bad input"));Assertions.assertThrows(IllegalArgumentException.class,()->service.sendWithMd5(100,"hello"));}}

注意:Mockito 静态 mock 需要 Mockito 3.4.0+ 和mockito-inline依赖。
Spring Boot 2.5+ / 3.x 的spring-boot-starter-test默认已包含,无需额外配置。


9 一句话总结

场景做法注解
Service 单测Mock 掉 Repository 和第三方 SDK@Mock+@InjectMocks
三方依赖直接用@Mock,只验证调用了对应方法@Mock
Controller 单测MockMvc 模拟 HTTP 请求,MockBean 掉 Service@WebMvcTest+@MockBean
注解启动 Spring?替换容器 Bean?用在什么测试
@MockService 单测(不启动 Spring)
@MockBeanController / 集成测试(需 Spring 容器)
@InjectMocks配合@Mock把 mock 塞进手动创建的被测类
http://www.jsqmd.com/news/706760/

相关文章:

  • 成都地区、H型钢、500X200X10X16、Q235B、包钢、现货批发供应 - 四川盛世钢联营销中心
  • 从Kubernetes原生调度到MCP 2026异构编排:7大不可逆演进路径,第5条已写入CNCF 2026技术路线图草案
  • 轻量级知识库引擎Lore:文件驱动架构与自托管部署实践
  • Linux USB驱动架构与性能优化实战
  • OpenClix:本地优先、配置驱动的移动端互动框架实战指南
  • 62、【Agent】【OpenCode】用户对话提示词(交互风格)(二)
  • 2026年正规制氮机技术解析:PSA变压吸附式工业制氧机/VPSA真空变压吸附式工业制氧机/中型工业制氧机/大型工业制氧站/选择指南 - 优质品牌商家
  • 机器学习入门实战:从零到房价预测模型
  • 2026川渝滇多雨区砾石聚合物地坪技术要点与案例:水洗石地坪厂家/沥青改色地坪厂家/砾石聚合物地坪厂家/透水地坪厂家哪家好/选择指南 - 优质品牌商家
  • ARM NEON与VFP编程:高性能并行计算实践
  • 全志A733处理器解析:八核SoC与RISC-V协处理器设计
  • 《别再写Service地狱了!用DDD重构我的项目全过程》
  • 成都地区、H型钢、294X200X8X12、Q235B、包钢、现货批发供应 - 四川盛世钢联营销中心
  • 深度学习工程实践:从数据准备到模型部署全流程指南
  • 湖州德清县GEO 代理适合跨境电商日常使用吗
  • 5G市场正步入精细化与战略性发展新阶段
  • 【2026年网易雷火春招- 4月26日-第一题- 喵居】(题目+思路+JavaC++Python解析+在线测试)
  • 成都地区、H型钢、390X300X10X16、Q235B、包钢、现货批发供应 - 四川盛世钢联营销中心
  • MiroFish-Offline:本地化AI模型部署与调优实战指南
  • AI群演请就位——个人博客(三)
  • AI 时代最大的谎言:你以为在学习,其实在欠债—思维决定上限的反焦虑框架
  • 达梦数据库-数据库存储加密02-记录总结
  • 成都地区、H型钢、400X400X13X21、Q235B、包钢、现货批发供应 - 四川盛世钢联营销中心
  • 每日一学:设计模式之观察者模式
  • 从虚拟化到容器化:华为云弹性计算架构的演进与实践
  • 重庆二手房历史交易信息2015-2023年
  • R语言机器学习模型保存与部署实战指南
  • 2026就近回收技术解析:西餐厅回收/高价回收/上门回收/专业回收/中餐馆回收/二手货回收/厨房设备回收/奶茶店回收/选择指南 - 优质品牌商家
  • NVIDIA Nemotron 3架构解析:智能体AI与混合Mamba-Transformer MoE设计
  • RNN与LSTM:序列预测模型原理与实战技巧