记录生活、记录历史

使用Mockito模拟静态方法

2021.06.13

1. 概述

某项目单元测试编码过程中,当前覆盖率大约在50%左右。一直无法得到有效提升的原因之一便是无法针对HttpUtil.postByJson这类方法进行Mock。

经过代码调查,将此类问题抽象为如何Mock静态方法?本教程将针对这一问题进行说明——如何针对静态方法的模拟进行说明。

2. 一个简单的静态类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * @author taliove
 * @author taliove 2019/4/10 09:42 新增针对不同的编码格式返回
 */
public class HttpUtil {
    private HttpUtil() {}
		/**
     * 使用JSON格式发送POST请求
     *
     * @param url   请求地址
     * @param param 请求参数
     * @return
     */
    public static String postByJson(String url, Object param) throws IOException {
        return "ok";
    }

		/**
     * 无参静态方法示例
     *
     * @return
     */
    public static String name() {
        return "test";
    }
}

为了方便于演示,此处封装了一个简单的返回"ok"的方法:postByJson

3. 依赖关系

当前阶段,该平台使用的mockito的版本号为2.x.x。该版本暂不支持静态方法的模拟。此处使用的是3.8.0版本。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
dependencies {
	// <https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy>
	implementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.10.20'

	testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.8.0'
	testImplementation group: 'org.mockito', name: 'mockito-inline', version: '3.8.0'
	// <https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy-agent>
	testImplementation group: 'net.bytebuddy', name: 'byte-buddy-agent', version: '1.9.3'
	// <https://mvnrepository.com/artifact/org.objenesis/objenesis>
	testImplementation group: 'org.objenesis', name: 'objenesis', version: '2.6'
}

mockito针对静态方法的模拟需要依赖包mockito-inline

mokito核心依赖于一个名为byte-buddy的库,当mocito找不到匹配的byte-buddyjar版本时,这个问题通常会发生。错误如下所示:

1
java.lang.IllegalStateException: Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null)

解决办法是查询MVN库,查看该mokito-core的编译依赖项,并将其引入即可。该查询地址的:https://mvnrepository.com/artifact/org.mockito/mockito-core/3.8.0。本例中,已将所有需要依赖的对象纳入。

4. 测试带参静态方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Test
public void twoArgsTest() {
    Map<String, String> param = new HashMap<>();
    param.put("account", "test");
    try {
        MockedStatic<HttpUtil> httpUtil = Mockito.mockStatic(HttpUtil.class);
        httpUtil.when(() -> HttpUtil.postByJson("<http://xx/login/login_cookie>", param)).thenReturn("bar");
        String resultStr = HttpUtil.postByJson("<http://xx/login/login_cookie>", param);
        assertSame("bar", resultStr);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Mockito 3.4.0开始,可以使用Mockito.mockStatic(Class <T> classToMock)方法来模拟对静态方法调用的调用。此方法为我们的类型返回一个MockedStatic对象,这是一个有范围的模拟对象。

因此,在上面的单元测试中,httpUtil变量表示具有线程局部显式作用域的模拟。

重要的是要注意,作用域模拟必须由激活该模拟的实体关闭。这就是为什么我们在try-with-resources构造中定义模拟程序的原因,以便当我们完成作用域块时自动关闭模拟程序。

5. 测试无参静态方法

1
2
3
4
5
6
7
8
@Test
public void noArgsTest() {
    try (MockedStatic<HttpUtil> httpUtil = Mockito.mockStatic(HttpUtil.class)) {
        httpUtil.when(HttpUtil::name).thenReturn("test");
				 assertSame("test", HttpUtil.name());
    }
    assertSame("tangf", HttpUtil.name());
}

6. 小结

在本篇短文中,举了两个示例来使用Mockito模拟静态方法。Mockito通过Lambda表达式为模拟静态对象提供了更优雅的解决方案。

大家在使用的过程中,需要结合自身项目的实际情况,多方位调试,以找准更优的融合方法。

7. 引用

  1. 如何mock静态方法
  2. mock初始化错误