Mockito-inline
是 Mockito
的一个分支,它提供了一种新的方法来模拟方法调用,称为内联模拟。内联模拟可以让你在调用方法时直接指定返回值,而不是在调用完方法后再设置返回值。这种方式的优点在于可以简化代码,并且更容易阅读和维护。相比于PowerMock,它对代码的侵入性更小(PowerMock
的原理是改写类的字节码),而且有更好的社区支持(PowerMock
已经两年多没有发布新版本)。所以,使用Mockito-inline
替代PowerMock
应该是未来的趋势。
安装
在pom.xml
中加入下面的依赖
1 2 3 4 5 6
| <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency>
|
如果已存在mockito-core
的依赖,则需要用这个依赖替换它。
使用
本教程对应的待测试类:
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 29 30 31 32
| public class StringUtil {
public static String randomStr() { RandomGenerator random = new Random(1); char[] chars = new char[8]; for (int i = 0; i < 8; i++) { if ((i % 2) == 0) { chars[i] = ((char) (97 + random.nextInt(26))); } else { chars[i] = ((char) (48 + random.nextInt(10))); } } return new String(chars); }
public static String empty() { return StrUtil.EMPTY; }
public static void printLine() { System.out.println(); }
public static void printWithLine(String s) { System.out.println(s); }
public static String trim(String s) { return StrUtil.trim(s); }
}
|
mock构造函数
StringUtil.randomStr
中有一个使用Random
的构造函数来初始化了一个RandomGenerator
,在测试类需要对其进行mock。
1 2 3 4 5 6 7 8 9 10 11
| @Test void randomStr() { try (MockedConstruction<Random> mockedConstruction = Mockito.mockConstruction(Random.class, (random, context) -> { Mockito.when(random.nextInt(ArgumentMatchers.anyInt())).thenReturn(1); }) ) { String randomStr = StringUtil.randomStr(); Assertions.assertEquals("b1b1b1b1", randomStr); } }
|
其中,对构造函数的mock只在try
中有效。(random, context) -> {...}
的第一个参数就是mock的构造函数返回的对象,可以在其函数体中进行进一步mock。
mock无参数的静态方法
StringUtil.empty
和StringUtil.printLine
是无参数的静态方法,使用Mockito-inline
测试这两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test void empty() { try (MockedStatic<StringUtil> mocked = Mockito.mockStatic(StringUtil.class)) { mocked.when(StringUtil::empty).thenReturn("xyz"); Assertions.assertEquals("xyz", StringUtil.empty()); } }
@Test void printLine() throws Exception { try (MockedStatic<StringUtil> mocked = Mockito.mockStatic(StringUtil.class)) { mocked.when(StringUtil::printLine).thenAnswer((invocation) -> { System.out.println("test"); return null; }); String text = SystemLambda.tapSystemOut(StringUtil::printLine); Assertions.assertEquals("test"+System.lineSeparator(),text); } }
|
mock带参数的静态方法
StringUtil.trim
和StringUtil.printWithLine
是带参数的静态方法,使用Mockito-inline
测试这两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Test void trim() { try (MockedStatic<StringUtil> mocked = Mockito.mockStatic(StringUtil.class)) { mocked.when(() -> StringUtil.trim(ArgumentMatchers.anyString())).thenReturn("abc"); Assertions.assertEquals("abc", StringUtil.trim(" xyz ")); } } @Test void printWithLine() throws Exception { try (MockedStatic<StringUtil> mocked = Mockito.mockStatic(StringUtil.class)) { mocked.when(() -> StringUtil.printWithLine(ArgumentMatchers.anyString())) .thenAnswer((invocation) -> { System.out.println(); return null; }); String text = SystemLambda.tapSystemOut(()->StringUtil.printWithLine("abc")); Assertions.assertEquals(System.lineSeparator(), text); } }
|
mockito-inline存在的问题
有些类不能mock
如java.io.File
这个类,mockito-inline就不能对其构造函数和一些方法(实例方法,如exists()
)进行mock,否则会报java.lang.StackOverflowError
的错误。开发者对这个问题的解释。