JUnit教程:Mockito结合使用指南,轻松测试复杂依赖场景

admin 综合编程开发技术 4


写 JUnit 测试时,你是不是也遇到过这种头疼事?想测试一个 Service 方法,结果它依赖了 DAO 层、Redis 缓存,甚至还要调第三方接口,光准备这些依赖就花了半天,测试还没跑起来就卡壳了?这时候要是不用点工具,单元测试根本写不下去。今天兔子哥就教你用 Mockito 结合 JUnit,轻松搞定这些复杂依赖场景,哪怕是零基础新手,跟着步骤做也能学会!

一、为啥测试复杂依赖这么难?Mockito 能帮上啥忙?


咱们先说说为啥带依赖的测试不好写。比如你要测一个用户服务的 “获取用户信息” 方法,这个方法得先查数据库(依赖 DAO),查不到再调远程接口(依赖 HttpClient),最后还要存缓存(依赖 RedisTemplate)。要是真的启动数据库、连远程接口,一来测试速度慢,二来环境不稳定,数据库没数据测试就失败,这哪行啊?
这时候 Mockito 就派上用场了。简单说,Mockito 是个 “模拟工具”,能帮你创建假的依赖对象(比如假的 DAO、假的 RedisTemplate),还能指定这些假对象的返回值。你不用关心它们实际怎么工作,只要告诉 Mockito“当调用这个方法时,返回这个结果”,测试就能跑起来了。
虽然 Mockito 好用,但也不是万能的。有朋友可能觉得用了它就能解决所有依赖问题,不过话说回来,过度依赖模拟可能让测试变得不真实,比如模拟的返回和真实情况差太远,测试通过了但实际运行却出问题。

二、先搭环境:Mockito 和 JUnit 怎么一起用?


想用 Mockito,得先把依赖加上,步骤很简单,新手别慌。
如果你用 Maven,在 pom.xml 里加这段(记得和 JUnit 5 的依赖一起加):
xml
<dependency><groupId>org.mockitogroupId><artifactId>mockito-coreartifactId><version>4.11.0version><scope>testscope>dependency><dependency><groupId>org.mockitogroupId><artifactId>mockito-junit-jupiterartifactId><version>4.11.0version><scope>testscope>dependency>

加完刷新 Maven,依赖就下来了。这里有个小细节,mockito-junit-jupiter 是让 Mockito 和 JUnit 5 配合的 “桥梁”,不加这个,有些注解可能不好使。
测试类开头还要加个注解:@ExtendWith (MockitoExtension.class),告诉 JUnit 用 Mockito 的扩展,这样才能用 Mockito 的注解。

三、核心注解学三个,模拟对象不求人


Mockito 的注解不少,但新手记三个最常用的就行,够用了:
第一个是 **@Mock**:用来创建假对象。比如你要模拟 DAO,就写 @Mock private UserDao userDao; 这样 userDao 就是个假的 DAO 对象,不用你自己 new,也不用连数据库。
第二个是 **@InjectMocks**:把 @Mock 创建的假对象 “塞” 到被测试对象里。比如你要测 UserService,它里面依赖 UserDao,就写 @InjectMocks private UserService userService; 这样 userService 里的 userDao 就会自动换成上面 @Mock 创建的假对象,不用手动 set 了,特方便。
第三个是when().thenReturn():指定假对象的返回值。比如你想让假 DAO 查 ID=1 的用户时返回 “张三”,就写 when (userDao.findById (1L)).thenReturn (new User (1L, "张三")); 这样调用 userDao.findById (1L) 就会返回你指定的用户,不用真查数据库。
有朋友可能会问,这些假对象能模拟所有方法吗?或许暗示不是所有方法都能完美模拟,比如有些静态方法、final 方法,Mockito 就不太好处理,这时候可能需要其他工具,不过新手遇到的场景里,大部分方法都能模拟。

四、实战案例:测试依赖 DAO 的 Service 层


光说不练假把式,咱们拿个实际场景举例。假设 UserService 里有个方法,依赖 UserDao 查用户:
java
@Servicepublic class UserService {@Autowiredprivate UserDao userDao;public String getUserName(Long id) {User user = userDao.findById(id);if (user == null) {return "未知用户";}return user.getName();}}

现在用 Mockito 写测试,步骤看这里:
java
@ExtendWith(MockitoExtension.class)public class UserServiceTest {// 模拟DAO对象@Mockprivate UserDao userDao;// 被测试的Service,会自动注入上面的假DAO@InjectMocksprivate UserService userService;@Testvoid testGetUserName_UserExists() {// 告诉假DAO:当查ID=1时,返回名字为张三的用户when(userDao.findById(1L)).thenReturn(new User(1L, "张三"));// 调用被测试方法String name = userService.getUserName(1L);// 断言结果是不是张三assertEquals("张三", name);}@Testvoid testGetUserName_UserNotExists() {// 告诉假DAO:当查ID=99时,返回nullwhen(userDao.findById(99L)).thenReturn(null);String name = userService.getUserName(99L);// 断言结果是未知用户assertEquals("未知用户", name);}}

运行这两个测试,都会通过。你看,不用连数据库,不用准备测试数据,假 DAO 按咱们的要求返回结果,测试就能跑起来,是不是很方便?
这里有个小技巧,测试完可以用 verify () 检查方法有没有被调用。比如加一句 verify (userDao).findById (1L); 能确认 userDao 的 findById 方法确实被调用了,参数是 1L,这样更严谨。

五、新手常踩的坑,这些地方要注意


用 Mockito 时,新手容易在这些地方出错,兔子哥提醒你避开:
第一个坑:忘了加 @ExtendWith (MockitoExtension.class)。结果 @Mock 注解没生效,假对象是 null,调用方法就报空指针,还以为是代码错了,其实就是少个注解。
第二个坑:模拟返回值时参数不对。比如 when (userDao.findById (1L)).thenReturn (...),但测试时调用的是 userDao.findById (2L),这时候会返回 null,因为没指定 2L 的返回值,新手容易在参数匹配上出错。
第三个坑:过度模拟。把所有依赖都换成假对象,结果测试通过了,但实际代码因为依赖之间的交互问题跑不起来。比如 Service 调用 DAO 后还要调用缓存,你只模拟了 DAO 却没管缓存,真实场景可能缓存出问题,但测试测不出来。
Mockito 内部的代理机制具体细节,可能需要查看源码才能完全搞懂,这里就不深入了,新手先会用就行。

六、兔子哥的一点心得,合理使用最重要


用了这么久 Mockito,兔子哥觉得它最大的价值是让测试摆脱对外部环境的依赖,不用等数据库、接口准备好就能写测试,大大提高效率。但一定要记住,模拟是为了测试核心逻辑,不是为了 “骗过” 测试。
建议大家模拟时尽量贴近真实场景,比如 DAO 查不到数据就返回 null,别模拟成返回不切实际的结果,不然测试就失去意义了。另外,别啥都模拟,简单的依赖(比如工具类)直接用真实对象就行,过度模拟反而让测试变复杂。
刚开始用可能会觉得注解多、规则多,但写两个例子就熟了。Mockito 就像个好帮手,用好了能让单元测试事半功倍,尤其是在依赖复杂的项目里,没它真不行。新手朋友别害怕,赶紧找个自己的项目试试,你会发现测试复杂依赖原来这么简单!

标签: mockito-junit-jupiter RedisTemplate

发布评论 2条评论)

  • Refresh code

评论列表

2025-10-24 23:45:51

JUnit Mockito 轻松测依赖

2025-10-25 01:10:06

JUnit+Mockito结合指南,轻松攻克复杂依赖测试