What’s Mockito ?

Mockito是Java单元测试开发框架。在写测试单元时它可以Mock(模拟)开发中一些未完成的接口或者网络断开、数据库连接错误等方法调用。Mockito很强大文中有限还有很多使用方式未提及,请参考官方文档。官方文档链接在文章尾部给出。
eq:when() 多个参数或者搭配List根据不同index设置返回结果。

QuickStart

为了更好的表达使用一个Mock DAO层的场景,数据库还没能正常使用时但是又急需测试功能逻辑是否正确。

1
2
3
4
5
6
mapper---|
UserMapper.java
mapperImpl---|
UserMapperImpl.java
service---|
UserService.java
1
2
3
4
5
6
7
8
//UserMapper.java
public interface UserMapper
{
User selectOne(Integer id);

void print();
}

1
2
3
4
5
6
7
8
9
10
11
public class UserMapperImpl implements UserMapper{
@Override
public User selectOne(Integer id) {
return null;
}

@Override
public void print() {
System.out.println("test");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//UserService.java UserService通过@Configuration配置由Spirng IOC管理
public class UserService
{
@Autowired
UserMapper userMapper;

public User getOne(Integer id)
{
return userMapper.selectOne(id);
}
}

第一步 添加依赖

Java 环境依赖

1
2
3
4
5
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.4.0</version>
</dependency>

SpringBoot 环境依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

第二步 mock对象与Stubbing

因为数据库无法正常使用的原因,但是service由依赖userMapper的selectOne方法,只能去mock一个假的userMapper。mock一个假的userMapper只需要使用注解@MockBeanSpringBoot会自动代替注入一个假的userMapper给service。具体原理是userMapper生成一个代理对象。

when()静态方法用于Stubbing(方法打桩),参数为非private 、final的方法调用。thenReturn设置被Stubbing的方法返回值。

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
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.mockito.Mockito.when;

@SpringBootTest
class MockitoDemoTests {

@MockBean
UserMapper userMapper;

@Autowired
UserService userService;

@Test
void mockUserMapper() {
when(userMapper.selectOne(0)).thenReturn(new User()); //这里mock的是userMapper的方法,service间接调用。
User user = userService.getOne(0);
assertThat(user,notNullValue()); //断言不为空
}

}
//Tests passed:1

More

built-in runner

使用Mockito的注解需要在JUnit5构建mock环境

加上类注解@ExtendWith(MockitoExtension.class)

Mock

使用mock创建一个代理对象。代理对象的方法调用返回都是java基本类型默认返回值。

1、使用mock方法

1
UserMapper userMapper= Mockito.mock(UserMapper.class); //不由Spring管理

2、使用@Mock注解 ,需要基于JUnit5环境。

1
2
@Mock
UserMapper userMapper; //不由Spring管理

​ 配合@InjectMocks注解可以注入到userService。

1
2
3
4
5
@Mock
UserMapper userMapper;

@InjectMocks
UserService userService;

3、使用Springboot的@MockBean注解,Spring会自动注入到测试环境里所需要依赖的对象。

1
2
@MockBean
UserMapper userMapper;

Spy

有时候需要在真实对象上mock方法,使用spy可以创建或者放入一个真实对象进行Stubbing,用spy创建的对象都是真实对象。

1、使用spy方法

1
UserMapper userMapper= Mockito.Spy(Object); 

2、使用@spy注解,需要基于JUnit5环境。

1
2
@Spy
UserMapper userMapper=new UserMapperImpl();

​ 配合@InjectMocks注解可以注入到userService。

1
2
3
4
5
@Spy
UserMapper userMapper;

@InjectMocks
UserService userService;

3、使用Springboot的@SpyBean注解

1
2
@SpyBean
UserMapper userMapper;

When

when可以对调用方法进行代理Stubbing方法返回值、方法异常

mock selectOne(0) 方法返回值,thenReturn参数为调用方法期望返回值。

1
when(userMapper.selectOne(0)).thenReturn(new User());

mock selectOne(0) 方法异常,当调用selectOne(0) 时会抛出异常。

1
when(userMapper.selectOne(0)).thenThrow(new TimeoutException());

当一个void方法不需要执行时可以使用 doNothing().when()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class MockitoDemoTests {

@Spy
UserMapper userMapper=new UserMapperImpl();

@Test
void mockUserMapper() {
doNothing().when(userMapper).print();
userMapper.print();
}
}
//没有任何输出

Argument matchers

当使用when去Stubbing方法时可以根据被调用方法传参做不同的返回值或异常。

1
2
3
4
5
6
7
when(userMapper.selectOne(0)).thenReturn(new User());
when(userMapper.selectOne(1)).thenReturn(new User());
User userId0 = userService.getOne(0);
User userId1 = userService.getOne(1);
assertThat(userId0,not(equalTo(userId1))); //断言是否是一个对象
//passed:1
//可以看到getOne(0)和getOne(1)不一致。

官方内置了很多类型的静态方法包含了很多场景。比如anyInt() 表示传参任何int类型数字都可以。

1
2
any()  //任何对象
any{基本类型}()
1
2
3
4
5
6
7
8
9
@Test
void mockUserMapper() {
User user=new User();
when(userMapper.selectOne(anyInt())).thenReturn(user);
User userId0 = userService.getOne(0);
assertThat(userId0,equalTo(user));//断言是否是一个对象
}
//passed:1
//可以看到user和userId0是一致。

需要注意的是 when(userMapper.selectOne(anyInt()))会被when(userMapper.selectOne(0))条件覆盖。
如果when里使用了官方内置类型any()方法其参数也应该使用。如果有指定参数可以使用eq()
when(xxx.xxx(any(),"text"))
when(xxx.xxx(any(),eq("text"))

Verification

验证被mock方法的动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class MockitoDemoTests {

@Spy
UserMapper userMapper=new UserMapperImpl();

@Test
void mockUserMapper() {
doNothing().when(userMapper).print();
userMapper.print();
verify(userMapper,times(1)).print(); //验证print()至少调用一次。
}
}
//passed:1

参考文档:

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans