Java 单元测试最趁手的 Mock 组件当属 Mockito,虽然它最初是基于继承来实现 Mock 的,所以对私有方法,私有属性,静态方法,final 类,final 方法,构造函数无能为力。于是有时不得不引入 JMockit 或 PowerMockit 来辅助。不过现在的 Mockito 功力有所增强。
首先是 Mockito 2.1.0 开始可以 Mock final 类和 final 方法,要在 classpath 下创建个文件 mockito-extensions/org.mockito.plugins.MockMaker
, 内容为 mock-maker-inline
。之前写过一篇介绍:Mockto 也能 Mock final 类和 final 方法了,其中也探索了它的实现细节,使用到了 ByteBuddy 修改字节码。
从 Mockito 3.4.0 通过类似的 mockto-extensions 扩展的方式,实现了对静态方法的 Mock。所有使用到的接口是 org.mockito.MockedStatic,它当前在 Mockito 3.7.7 中还是一个试验性方法 @Incubating,能拿来用就行。
引入 mockito-inline 依赖
那么来看看 Mockito 是如何 Mock 静态方法,网上的文章一般是说要用 mockito-inline
替换 mockito-core
依赖,也就是要在 pom.xml 中的依赖由通常的
1 2 3 4 5 6 |
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.7.7</version> <scope>test</scope> </dependency> |
换成
1 2 3 4 5 6 |
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>3.7.7</version> <scope>test</scope> </dependency> |
那么由 mockito-core
到 mockito-inline
发生了什么呢,看 mockito-inline 的 pom.xml 文件就知道,它其实内部依赖了 mockito-core
。使用了 mockito-inline
后效果如下:
实质上 mockito-inline
就是给 mockito-core 添加了两个插件配置,分别是 org.mockito.plugins.MockMaker
和 org.mockito.plugins.MemberAccessor
,而前者也是用于 Mock final 类和 final 方法的。至于 Mock 静态方法是由前者还是后者支持的需要进一步研究。
要是不想直接引入 mockito-inline
的话,自己在 classpath 下创建相同的文件及其内容也是可以的。
测试中 Mock 静态方法
有了 mockito-inline
接着尝试下用代码如何 Mock 一个静态方法,先创建一个含有静态方法的待测试类 Utils
1 2 3 4 5 6 7 8 9 10 |
package yanbin.blog; import java.time.LocalDate; public class Utils { public LocalDate getCurrentDate() { return LocalDate.now(); } } |
测试 UtilsTest
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 27 28 |
package yanbin.blog; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.time.LocalDate; import static org.junit.Assert.assertEquals; @RunWith(MockitoJUnitRunner.class) public class UtilsTest { private Utils target = new Utils(); @Test public void testGetCurrentDate() { LocalDate yearOf2000 = LocalDate.of(2000, 1, 1); try (MockedStatic theMock = Mockito.mockStatic(LocalDate.class)) { theMock.when(LocalDate::now).thenReturn(yearOf2000); System.out.println(target.getCurrentDate()); assertEquals(2000, target.getCurrentDate().getYear()); } } } |
测试通过,当前日期是 2021-02-03, 打印的日期是 2000-01-01, 又重回到了 2000 年。
这里我们用到了 Mockito.mockStatic(LocalDate.class)
Mock 了 LocalDate 的所有的静态方法,在它 try 块外端调用 LocalDate 的静态方法是可以, 其间不用
theMock.when(LocalDate::now).thenReturn(yearOf2000)
而直接调用 LocalDate 的其他静态方法,如 LocalDate.of(2000, 1, 1) 则会报错,像下面的代码
1 2 3 4 5 6 7 8 9 |
@Test public void testGetCurrentDate() { try (MockedStatic theMock = Mockito.mockStatic(LocalDate.class)) { theMock.when(LocalDate::now).thenReturn(LocalDate.of(2000, 1, 1)); System.out.println(target.getCurrentDate()); assertEquals(2000, target.getCurrentDate().getYear()); } } |
运行后报错
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at yanbin.blog.UtilsTest.testGetCurrentDate(UtilsTest.java:21)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
at java.time.LocalDate.of(LocalDate.java:266)
at yanbin.blog.UtilsTest.testGetCurrentDate(UtilsTest.java:21)
............
也就是说用 try (MockedStatic theMock = Mockito.mockStatic(LocalDate.class)) { ... }
一旦 Mock 一个类,那么它所有的静态方法都被 Mockito 接管。
一张图大概看一下它的内部实现
也是用的 ByteBuddy
再测试一下是哪个 Mockito 的插件在起作用
我们把依赖从 mockito-inline
恢复到 mockito-core
, 然后在 classpath 下自己创建 mockito-extensions/org.mockito.plugins.MockMaker
并内容
mock-maker-inline
重新运行最前面的 UtilsTest, 测试通过,说明 mock-maker-inline
同时支撑了对 final 类,final 方法和静态方法的 Mock。那就是说明 mockito-extensions/org.mockito.plugins.MemberAccessor
的 member-accessor-module
增强了 Mockito 的其他特性,初步猜测是可用来替换测试类的内部成员属性值,新任务又来了。
链接:
本文链接 https://yanbin.blog/mockito-3-4-0-mock-static-method/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 2.5 通用 (CC BY-NC-SA 2.5) 进行许可。
随机文章: |