基于 Mockito 框架的 Mock 测试

1.什么是 Mock 测试

Mock 通常是指,在测试一个对象 A 时,我们构造一些假的对象(一般是不容易构造或者不容易获取的对象,比如测试类所依赖的实现类、第三方接口、数据库操作对象)来模拟与 A 之间的交互,这些对象被称为 Mock 对象,而 Mock 对象的行为是我们事先设定且符合预期。通过这些 Mock 对象来测试 A 在正常逻辑,异常逻辑或压力情况下工作是否正常。

下面是一个例子:

当我们需要测试 OrderService 时,按照我们常规的做法呢,都是要先准备好 redis,跟 db 的环境,然后构造UserService 跟 CouponService 注入进来,此时需要构建完整的依赖树,其过程是比较繁琐的,万一数据库连不上,依赖找不到,服务挂了... 时间一长可能会打击我们对项目进行单测的积极性,所以这时候很有必要寻求一种优雅的方式来解决。即,把这些依赖都转成 Mock 对象,作为模拟,将测试的重点专注于开发的功能逻辑。

2.常用注解

2.1@Mock

创建一个模拟对象,并注入到测试类中。通常和 @InjectMocks 注解一起使用,用于为测试类中的被测对象注入模拟对象。当你使用 @Mock 注解一个对象时,Mockito 会为你创建一个该对象的模拟实例。这个模拟实例的行为(即方法的返回值和它们被调用的方式)可以被精确地控制和验证。默认情况下,模拟对象上的所有方法调用都会返回 null(对于引用类型)或默认值(对于原始类型)。

可以通过插桩的方式来模拟对象的行为。

2.2@Spy

用于创建一个部分模拟对象,它也属于 mock 对象的一种。但与@Mock不同,@Spy创建的是一个实际对象的代理,并且允许你选择性地模拟对象中的某些方法。默认情况下,所有未被特别模拟的方法都会调用实际对象的方法,通常与 doReturn/doThrow 等方法结合使用。一般是加在要测试的方法所在的类对象上,即加了 @InjectMocks 注解的位置。

2.3@InjectMocks

一般标注在被测对象上(必须是实现类),因为 mockito 会为添加了 @InjectMocks 注解的属性创建对应的实例对象。默认创建的对象就是一个普通的对象。经常需要配合 @Spy 注解使其变为默认调用真实方法的 mock 对象(可以理解为一个支持插桩的真实对象)。此外,mockito 会将 @Mock 和 @Spy 标注的对象注入到添加了 @InjectMocks 注解的被测对象中。

java 复制代码
@InjectMocks
@Spy
private UserServiceImpl UserServiceImplTest;

@Mock
private UserFeatureService userFeatureService;

@Mock
private List<String> mockList;

一点总结:

方法插桩 方法不插桩 作用对象
Mock 对象 执行插桩逻辑 返回 mock 对象的默认值(不会去调用真实实现类方法) 被测试类的依赖项
Spy 对象 执行插桩逻辑 调用真实方法 被测试的实现类

3.常见操作

3.1插桩

  • when(xxxService.someMethod()).thenXxx(...):其中 xxxService 可以是 mock 对象。
  • doXXX().when(xxxService).someMethod():其中 xxxService 可以是 mock/spy 对象。

推荐是使用 doXXX()... 的插桩方式,因为对于 spy 对象,如果采用 when(...)... 的方式,会先执行真实的方法,比如会先执行一段打印的逻辑,而这不是我们想要的(即使由于插桩,最终返回的是插桩时指定的返回)。

常见的几种用法:

  • 指定返回
  • 指定抛出异常
  • 多次插桩
  • thenAnswer 指定实现自定义逻辑的插桩
java 复制代码
@Test
public void test()(
    when(mockList.get(anyInt())).thenAnswer(newAnswer<String>(){
        /**
        *泛型表示要插桩方法的返回值类型
        */
        @override
        public String answer(InvocationOnMock invocation) throws Throwable{
            // getArgument表示获取插桩方法(此处就是List.get)的第几个参数值
            Integer argument = invocation.getArgument(index: 0, Integer.class);
            return String.valueof(argument* 100);
        }
});
    String result = mockList.get(3);
    Assertions.assertEquals(expected:"3ee",result);
}

3.2执行真实的方法

Mock 对象也可以执行真实的方法,只要在插桩时指定让他去调用真实方法即可。

Spy 对象,不插桩时即是执行真实方法。

3.3verify

可以用于验证某个方法是否被调用、被调用了几次、至少/至多调用了几次。且可以指定匹配的参数。此外,即使该方法被调用多次,但参数未匹配上,那也是不算的。

Verify 的另一个作用是对没有返回值的方法进行验证,证明其至少是被调用过的。

java 复制代码
@Mock
private UserService userService;

@Test
public void test1() {
    userService.addUser("张三", "25", "男")
    // 通过
    verify(userService, times(1)).addUser("张三","25","男");
    // 不通过
    verify(userService, times(1)).addUser("李四","25","男");
    // 通过
    verify(userService, times(1)).addUser(anyString(),anyString(),anyString());
}

3.4断言

断言可用于对有返回值的方法进行判断。

也可以对没有返回值的方法进行判断:

java 复制代码
Assertions.assertDoesNotThrow(() -> userServiceImplUnderTest.add("张三","25"));

4.基本思路

  1. 对于非测试方法进行 mock(一般是插桩),一般是数据库,或其他类方法的调用;
  2. 调用需要测试的方法;
  3. 实时修改(插桩、添加断言等);

5.参考

  1. 视频,有案例,讲的挺清楚的。mockito加junit搞定单元测试
  2. Java单元测试神器之Mockito - 掘金
  3. 这篇很全:Mockito测试框架入门与使用
  4. 对mock基本概念的介绍挺好的:单元测试 - Mockito 详解
  5. 单元测试 - Mockito 详解
相关推荐
叶落无痕5210 小时前
Electron应用自动化测试实例
前端·javascript·功能测试·测试工具·electron·单元测试
汽车仪器仪表相关领域11 小时前
工况模拟精准检测,合规减排赋能行业 ——NHASM-1 型稳态工况法汽车排气检测系统项目实战经验分享
数据库·算法·单元测试·汽车·压力测试·可用性测试
码农水水13 小时前
大疆Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·面试·职场和发展·单元测试·php
卓码软件测评18 小时前
CMA-CNAS软件测评报告机构【Apifox动态Mock响应处理复杂业务逻辑设计】
测试工具·性能优化·单元测试·测试用例
孙琦Ray18 小时前
GitHub开源项目日报 · 2026年1月7日 · 本期热门开源全景
单元测试·开源·前端调试·浏览器自动化·知识管理·ai代理·跨语言序列化
程序员三藏19 小时前
单元测试详解
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
卓码软件测评2 天前
CMA/CNAS双资质软件测评机构【Apifox高效编写自动化测试用例的技巧和规范】
测试工具·ci/cd·性能优化·单元测试·测试用例
回眸&啤酒鸭2 天前
【回眸】Tessy 单元测试软件使用指南(五)进阶报错之解决指南(含泪整理)
单元测试
小二·2 天前
前端测试体系完全指南:从 Vitest 单元测试到 Cypress E2E(Vue 3 + TypeScript)
前端·typescript·单元测试
无心道人c2 天前
SonarQube7.6实现C#自定义规则
单元测试·自动化·sonar