单元测试工具Mockito

为什么使用Mockito

项目中代码需要进行单元测试。测试人员用黑盒测试的软件功能,无法完全覆盖代码中的逻辑,所以要有代码的单元测试。

spring 的单元测试有以下缺点:

1.每次执行测试方法都必须启动spring容器。当项目规模较大、配置较为复杂时,会很慢。

2.如果使用数据库如mysql, 还会涉及插入数据、数据库自增ID增加问题、bug引起的大量脏数据

3.如果使用数据库,需要根据ID查询数据,必须要插入一条数据,每次执行单元测试都需要

如果只想测试service代码的正确性,使用Mockito就可以解决。

spring 的单元测试也是有用的,对容器、对组件的验证还是需要的。想要测试哪里选择不同的单元测试工具。

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestDomain {

private Long id;

private String name;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
1
2
3
4
5
6
7
8
9
public interface TestMapper {

Long getMax();

TestDomain getById(Long id);

int save(TestDomain testDomain);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestService {

private TestMapper testMapper;

public Long method1() {
Long id = testMapper.getMax();
return id;
}

public void save(TestDomain testDomain) {
int i = testMapper.save(testDomain);
if (i <= 0) {
throw new RuntimeException("插入失败");
}
}

public TestDomain getById(Long id) {
return testMapper.getById(id);
}

}

使用Mockito

https://github.com/mockito/mockito

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
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
49
50
51
52
53
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {

@InjectMocks
private TestService testService;

@Mock
public TestMapper testMapper;


@Test
public void method1() {
Mockito.when(testMapper.getMax()).thenReturn(20L);
long id = testService.method1();
Assert.assertEquals(id, 20L);
}

@Test
public void save() {
TestDomain testDomain = new TestDomain();
testDomain.setId(39L);
testDomain.setName("39");
Mockito.when(testMapper.save(testDomain)).thenReturn(1);
testService.save(testDomain);

Mockito.when(testMapper.save(testDomain)).thenReturn(0);

try {
testService.save(testDomain);
} catch (RuntimeException e) {
Assert.assertEquals("插入失败", e.getMessage());
}
}

@Test
public void getById() {
TestDomain testDomain = new TestDomain();
testDomain.setId(39L);
testDomain.setName("39");
Mockito.when(testMapper.getById(39L)).thenReturn(testDomain);
TestDomain testDomain1 = testService.getById(39L);
Assert.assertEquals(testDomain, testDomain1);
}

}

使用powermock

去掉mockito-core、junit依赖,在pom.xml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<powermock.version>2.0.2</powermock.version>
</properties>

<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

因为powermock内已经依赖这两个包了。

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
49
50
51
52
53
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
public class TestServiceTest {

@InjectMocks
private TestService testService;

@Mock
public TestMapper testMapper;


@Test
public void method1() {
Mockito.when(testMapper.getMax()).thenReturn(20L);
long id = testService.method1();
Assert.assertEquals(id, 20L);
}

@Test
public void save() {
TestDomain testDomain = new TestDomain();
testDomain.setId(39L);
testDomain.setName("39");
Mockito.when(testMapper.save(testDomain)).thenReturn(1);
testService.save(testDomain);

Mockito.when(testMapper.save(testDomain)).thenReturn(0);

try {
testService.save(testDomain);
} catch (RuntimeException e) {
Assert.assertEquals("插入失败", e.getMessage());
}
}

@Test
public void getById() {
TestDomain testDomain = new TestDomain();
testDomain.setId(39L);
testDomain.setName("39");
Mockito.when(testMapper.getById(39L)).thenReturn(testDomain);
TestDomain testDomain1 = testService.getById(39L);
Assert.assertEquals(testDomain, testDomain1);
}

}

spring boot 与powermock整合

因为spring boot的父pom中已经引入了mockito,所以要注意,如果运行时报类不存在问题,可以明确加入mockito及版本解决。

介绍

org.mockito.Mockito

org.mockito.Mockito是mockito提供的核心api,提供了大量的静态方法,用于帮助我们来mock对象,验证行为等等,然后需要注意的是,很多方法都被封装在了MockitoCore类里面,下面对一些常用的方法做一些介绍。

mock:构建一个我们需要的对象;可以mock具体的对象,也可以mock接口。
spy:构建监控对象
verify:验证某种行为
when:当执行什么操作的时候,一般配合thenXXX 一起使用。表示执行了一个操作之后产生什么效果。
doReturn:返回什么结果
doThrow:抛出一个指定异常
doAnswer:做一个什么相应,需要我们自定义Answer;
times:某个操作执行了多少次
atLeastOnce:某个操作至少执行一次
atLeast:某个操作至少执行指定次数
atMost:某个操作至多执行指定次数
atMostOnce:某个操作至多执行一次
doNothing:不做任何处理
doReturn:返回一个结果
doThrow:抛出一个指定异常
doAnswer:指定一个操作,传入Answer
doCallRealMethod:返回真实业务执行的结果,只能用于监控对象

ArgumentMatchers

用于进行参数匹配,减少很多不必要的代码

anyInt:任何int类型的参数,类似的还有anyLong/anyByte等等。
eq:等于某个值的时候,如果是对象类型的,则看toString方法
isA:匹配某种类型
matches:使用正则表达式进行匹配

OngoingStubbing

OngoingStubbing用于返回操作的结果。

thenReturn:指定一个返回的值
thenThrow:抛出一个指定异常
then:指定一个操作,需要传入自定义Answer;
thenCallRealMethod:返回真实业务执行的结果,只能用于监控对象。

注解

@RunWith
在测试类类名上添加 @RunWith(PowerMockRunner.class) 注解代表该测试类使用 PowerMock。必须添加。

@PrepareForTest

这个注解的作用就是告诉 PowerMock 哪些类是需要在字节码级别上进行操作的。也就是需要 mock 某些包含 final、static 等方法的类时使用,使用方法:@PrepareForTest({System.class, LogUtils.class}),在方法中有new对象的时候也需要在@PrepareForTest中声明。

@PowerMockIgnore

PowerMock 是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的。@PowerMockIgnore 注解告诉 PowerMock 忽略哪些包下的类,从而消除类加载器引入的 ClassCastException。使用方法:@PowerMockIgnore({“javax.management.”, “javax.net.ssl.”, “javax.script.*”})

@SuppressStaticInitializationFor

告诉 PowerMock 哪些包下的类需要被抑制静态初始化,包括 static 代码块或者 static 变量的初始化。防止因静态初始化导致的错误。使用方法:

@SuppressStaticInitializationFor({“com.xxx.SmsServiceImpl”})

@Mock

mock 待测类的普通属性,最常见的就是通过 Spring @Autowired 自动注入的 bean。这类属性并非 final,也并非 static,只需要在测试类中使用 @Mock 注解,Pwermock 框架就能自动生成 mock 对象,并自动注入到 @InjectMock 修饰的待测类中。
也可以使用比较通用的 mock() 方法,调用 mock(XXX.class),能生成一个 mock 的 XXX 对象,然后通过反射获取待测类的字段,再将 mock 对象赋给字段。一般用于不是由 Spring @Autowired 注入的普通属性。


单元测试工具Mockito
http://hanqichuan.com/2023/07/27/java/单元测试工具Mockito/
作者
韩启川
发布于
2023年7月27日
许可协议