单元测试第六篇,演示在单元测试时,如何拦截某些方法的访问,让它们返回我们想要的值,而不是执行该方法本身,以便其它代码的测试和验证。
相关代码已经放在Github上:源代码

注:前面5篇,是介绍在单元测试中避免访问外部中间件,但有时,我们想进行测试的代码,调用了一些我们并不想测试的函数方法,此时,就可以用mockito框架来mock掉这些不想测试的方法。

下面简述开发过程:
1、首先,还是正常建立项目,并完成一些常规的业务逻辑开发;

2、项目添加mockito库的引用,打开pom.xml,添加:

<!-- https://site.mockito.org/ 说mockito-all已经停止,推荐用mockito-core -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.5.13</version>
    <scope>test</scope>
</dependency>

注1:scope为test,是仅在单元测试时引入,运行时并不引入。

3、application.yml配置文件无需修改.

4、在单元测试代码里,对不关注方法所在的类,创建MockBean对象:

@MockBean // 本来是@Autowired,改用MockBean
private BusinessService businessService;

注:用MockBean注解的对象,所有方法都只返回null,而不会真正执行原来的方法了。

5、Mock无参方法举例:

// mock,当调用 requestBaiduHtml方法时,直接返回后面的字符串
Mockito.when(businessService.requestBaiduHtml()).thenReturn("我是Mock后的百度");
String ret = businessService.requestBaiduHtml(); 
// 断言,返回值是否匹配 
Assert.isTrue(ret.equals("我是Mock后的百度"), "mock失败?");

ret = businessService.requestSinaHtml();
// 没有mock的方法,会直接返回null
Assert.isTrue(ret == null, "啥情况?");

6、Mock有参方法举例:

// 对requestByPara,且参数为123才进行mock
Mockito.when(businessService.requestByPara("123")).thenReturn("mock参数123");
// 参数是123,返回值是 mock参数123
ret = businessService.requestByPara("123");
Assert.isTrue(ret.equals("mock参数123"), "啥情况?");
// 参数不是123,不会mock,返回null
ret = businessService.requestByPara("456");
Assert.isTrue(ret == null, "啥情况?");

// 指定任意参数都要返回值
Mockito.when(businessService.requestByPara(ArgumentMatchers.anyString())).thenReturn("mock任意参数");
ret = businessService.requestByPara("456");
Assert.isTrue(ret.equals("mock任意参数"), "啥情况?");

注:ArgumentMatchers.anyString 不支持null参数,需要特别注意

7、Mock无返回值的void方法举例:

ArgumentCaptor<Object> arg1 = ArgumentCaptor.forClass(Object.class);
ArgumentCaptor<Long> arg2 = ArgumentCaptor.forClass(Long.class);
// doNothing忽略方法调用,并把方法的2个参数进行捕获
Mockito.doNothing().when(businessService).noReturnMethod1(arg1.capture(), arg2.capture());
// 方法调用
String realArg1 = "我是参数1";
long realArg2 = 123567;
businessService.noReturnMethod1(realArg1, realArg2);
// 对捕获的参数进行断言
Assert.isTrue(realArg1.equals(arg1.getValue()), "");
Assert.isTrue(realArg2 == arg2.getValue(), "");

// void方法测试2,替换void方法
Mockito.doAnswer(invocation -> {
    Object objArg = invocation.getArgument(1);
    Long longArg = invocation.getArgument(0);
    System.out.println(objArg + "===" + longArg);

    // 对捕获的参数进行断言
    Assert.isTrue(realArg1.equals(objArg), "");
    Assert.isTrue(realArg2 == longArg, "");

    return invocation.callRealMethod();// 需要时,这里可以回调原始方法
}).when(businessService).noReturnMethod2(ArgumentMatchers.anyLong(), ArgumentMatchers.any());
businessService.noReturnMethod2(realArg2, realArg1);

注:源码中还有方法返回异常的mock演示

8、SpyBean注解
这个注解与MockBean的区别,主要就是MockBean生成的对象,没mock时不执行任何代码,并返回null,而SpyBean默认会执行真实方法体,等同于 上面的 callRealMethod

9、mockito库还提供了verify校验方法断言
这个我很少用,简单代码Demo看下吧:

// 初始化
BusinessService mockObj = Mockito.mock(BusinessService.class);
DbController controller = new DbController(mockObj);

// 执行业务逻辑 各1次
controller.getSina();
controller.getBaidu();

// 断言,requestBaiduHtml方法 只调用了1次
Mockito.verify(mockObj).requestBaiduHtml();// 等效于 verify(mockObj, Mockito.times(1))
// 断言,requestSinaHtml方法 只调用了1次
Mockito.verify(mockObj, Mockito.times(1)).requestSinaHtml();
// 断言,requestSinaHtml方法 最少调用了1次(可以1次或n次)
Mockito.verify(mockObj, Mockito.atLeastOnce()).requestSinaHtml();
Mockito.verify(mockObj, Mockito.atLeast(1)).requestSinaHtml();
// 断言,requestSinaHtml方法 最多只能调用5次(可以0次到5次)
Mockito.verify(mockObj, Mockito.atMost(5)).requestSinaHtml();
// 断言,requestByPara方法 没调用过
Mockito.verify(mockObj, Mockito.never()).requestByPara("123a");

// 执行业务逻辑
controller.getWithPara("123");
controller.getWithPara("123a");
// 断言,requestByPara方法调用了2次
Mockito.verify(mockObj, Mockito.times(2)).requestByPara(ArgumentMatchers.any());
// 断言,requestByPara方法+参数123 只调用了1次
Mockito.verify(mockObj).requestByPara("123");
// 断言,requestByPara方法+参数123a 只调用了1次
Mockito.verify(mockObj).requestByPara("123a");
// 断言,requestByPara方法+参数123b 没调用了过
Mockito.verify(mockObj, Mockito.never()).requestByPara("123b");

注:以上代码,在提供的源码Demo里都有。

如果你在运行时有问题,可以下载上面的源码,进行对比,看看你的代码哪里有问题。