前言

一个服务经常会关联多个其他服务来一起完成一项工作,而我们在写测试用例的时候是聚焦在某个小单元的业务上,所以外部的一些服务我们可以给他屏蔽掉。这边的屏蔽不是完全的不去管它,而是希望外部服务虽然没有真实触发,但是它可以模拟返回我们要的结果。Mock 就是用来帮助我们完成这个过程的。

1. @Mocked

普通对象的 Mock 可以借助 @Mocked 完成。主要由 录制回放验证 三步骤完成,是对某个类的所有实例的所有方法进行完整的模拟方式。
针对类及所有实例的的整体模拟,未写录制的方法默认返回0 或 null。 由于作用范围比较广,建议在某个具体的单测方法参数里面定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 被测试类
*/
public class App {

public String say() {
return "Hello World";
}

public String say2(){
return "Hello World 2";
}

public static String staticSay() {
return "Still hello world";
}
}
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
33
34
35
36
/**
* 测试类
*/
public class AppTest {

/**
* 针对类及所有实例的的整体模拟,未写录制的方法默认返回0,null等
*/

@Test
public void testSay(@Mocked App app) {

//录制,定义被模拟的方法的返回值,可以录制多个行为,写在一个大括号里也可以,多个大括号隔开也可以
new Expectations() {{
app.say();
result = "say";
}};

//回放,调用模拟的方法
System.out.println(app.say()); //say
System.out.println(new App().say()); //say
System.out.println(App.staticSay()); //null

//验证
new Verifications() {{
//验证say模拟方法被调用,且调用了2次
app.say();
times = 2;

//验证staticSay模拟方法被调用,且调用了1次
App.staticSay();
times = 1;
}};

}
}

2. @Injectable

@Injectable 和 @Mocked 的方式很像,区别是 @Injectable 仅仅对当前实例进行模拟,而 @Mocked 是对所有实例。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 测试类
*/
public class AppTest001 {

/**
* 仅针对当前实例的整体模拟
*/
@Injectable
App app;

@Test
public void testSay() {

//录制
new Expectations() {{
app.say();
result = "say";
}};

//回放
System.out.println(app.say()); //say,模拟值
System.out.println(app.say2()); //null,模拟默认值

final App appNew = new App();
System.out.println(appNew.say()); //Hello World,未被模拟,方法实际值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值

//验证
new Verifications() {
{
//验证say模拟方法被调用
app.say();
times = 1;
}
{
appNew.say();
times = 1;
}
{
//验证staticSay模拟方法被调用,且调用了1次
App.staticSay();
times = 1;
}
};

}
}

3. Expectations

录制,带参数表示局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 测试类
*/
public class AppTest002 {

@Test
public void testSay() {

final App app = new App();

//录制,带参数表示局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
new Expectations(App.class) {{
app.say();
result = "say";
}};

//回放
System.out.println(app.say()); //say,模拟值
System.out.println(app.say2()); //Hello World 2 ,未被模拟,方法实际值
System.out.println(new App().say()); //say,模拟值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值

}
}

4. MockUp

局部模拟,不是简单的返回,可重写原有方法的逻辑,比较灵活,推荐使用。(静态方法也能用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 测试类
*/
public class AppTest003 {

@Test
public void testSay() {
//局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
new MockUp<App>(App.class){
@Mock
String say(){
return "say";
}
};
//回放
System.out.println(new App().say()); //say,模拟值
System.out.println(new App().say2()); //Hello World 2,未被模拟,方法实际值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值

}
}

5. Capturing

mocked 的增强版本,可以 mock 接口和静态类,如果 Capturing 某个接口,那么该接口下的所有方法都会自动被 mock

1
2
3
4
5
6
7
8
9
10
@Test
public void sonarCallBak(@Capturing EsClient esClient) throws UnsupportedEncodingException {
new Expectations() {
{
esClient.createDoc(withInstanceOf(SimpleDocVo.class), withInstanceOf(PipelineJobSonar.class));
result = "{}";
times = 1;
}
};
...