zjjfly's blog

Java,Clojure,Scala...

0%

Mockito-inline教程

Mockito-inlineMockito 的一个分支,它提供了一种新的方法来模拟方法调用,称为内联模拟。内联模拟可以让你在调用方法时直接指定返回值,而不是在调用完方法后再设置返回值。这种方式的优点在于可以简化代码,并且更容易阅读和维护。相比于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.emptyStringUtil.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)) {
//printLine没有返回值,所以需要使用thenAnswer进行mock
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.trimStringUtil.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)) {
//mock有参数的方法,需要把要mock的方法调用放在lambda中
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)) {
//printWithLine没有返回值,所以需要使用thenAnswer进行mock
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的错误。开发者对这个问题的解释