目录
1.什么是Mockito?
2. Mockito 简介
3. 在 SpringBoot 单元测试中使用 Mockito
4.新建实体类,创建接口测试
5. Mock 的限制
6.Mock 使用
Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,避免你为了测试一个方法,却要自行构建整个 bean 的依赖链。
像是以下这张图,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很难测,因为你的返回结果会直接的受外部服务影响,导致你的单元测试可能今天会过、但明天就过不了了。
而当我们引入 mock 测试时,就可以创建一个假的对象,替换掉真实的 bean B 和 C,这样在调用B、C的方法时,实际上就会去调用这个假的 mock 对象的方法,而我们就可以自己设定这个 mock 对象的参数和期望结果,让我们可以专注在测试当前的类 A,而不会受到其他的外部服务影响,这样测试效率就能提高很多。
说完了 Mock 测试的概念,接下来我们进入到今天的主题,Mockito。
Mockito 是一种 Java Mock 框架,他主要就是用来做 Mock 测试的,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 Mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用。
像是 Mockito 可以在单元测试中模拟一个 Service 返回的数据,而不会真正去调用该 Service,这就是上面提到的 Mock 测试精神,也就是通过模拟一个假的 Service 对象,来快速的测试当前我想要测试的类。
目前在 Java 中主流的 Mock 测试工具有 Mockito、JMock、EasyMock等等,而 SpringBoot 目前内建的是 Mockito 框架。
1.7.1
org.powermock
powermock-api-mockito2
${powermock.version}
test
org.mockito
mockito-core
test
org.powermock
powermock-module-junit4
${powermock.version}
test
1. 新建实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private String name;
private String sex;
private String age;
}
2. 创建UserService
import java.util.Map;
/**
* @Service 不加无法注入
*/
public class UserService {
public User saveUser(Map map) {
return new User("1","1", "I'm mock 3");
}
public int getInt(int i){
return i;
}
public int getInt(){
return 1;
}
}
3.创建测试类
使用RunwWith(MockitoJUnitRunner.class)(也可以使用SpringBootRunner.class)来进行mocktio测试,注解@Mock标记一个类或者接口是需要被mock的,在Mock的过程中就相当于@Resource,但是注意一点是Mock是继承or实现了Mock的类,所以Mock出来的方法,全是null或者返回值为null。
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
@Mock
private UserService userService;
@Before
public void setUp() throws Exception {
System.out.println("Before");
}
@Test
public void saveProjectBase() {
}
}
1.我们平常习惯性测试
这时候我们先不使用 Mockito ,而是真的去调用一个正常的 Spring bean ,测试类写法如下。其实就是很普通的注入 userService bean,然后去调用他的方法。
@RunWith(MockitoJUnitRunner.class)
public class MockTests {
@Resource
private UserService userService;
@Before
public void setUp() throws Exception {
System.out.println("Before");
}
@Test
public void saveProjectBase() {
int i = userService.getInt(1);
System.out.println(i);
}
}
2. mock 的使用
后面自定返回结果,需要和方法返回结果类型一致
Mockito.when( 对象.方法名() ).thenReturn( 自定义结果 )
使用 Mockito 模拟 Bean 的单元测试具体实例如下:
@Test
public void saveProjectBase() {
// 定义当调用mock userService的getUser()方法,并且参数为1时,就返回age为200、name为I'm mock3的user对象
Mockito.when( userService.getUser(1)).thenReturn(new User("1","1", "I'm mock 3"));
// 返回的会是名字为I'm mock 3的user对象
User user = userService.getUser(1);
System.err.println(user.toString());
// 返回的会是 null
User user1 = userService.getUser(2);
System.err.println(user1.toString());
}
@Test
public void saveProjectBase() {
// 定义当调用mock userService的getUser()方法,并且参数为1时,就返回age为200、name为I'm mock3的user对象
Mockito.when( userService.getUser(1)).thenReturn(new User("1","1", "I'm mock 3"));
// 返回的会是名字为I'm mock 3的user对象
User user = userService.getUser(1);
System.err.println(user.toString());
// 返回的会是 null
User user1 = userService.getUser(2);
// 不能为Null
Assert.assertNotNull(user1); // 如若为null 会提示错误 java.lang.AssertionError
Assert.assertEquals(user.getAge(), "200");
Assert.assertEquals(user.getName(), "I'm mock 3");
// 验证 userService 的 getInt()这个方法是否被调用过
Mockito.verify(userService).getInt();
}
Mockito 除了最基本的 Mockito.when( 对象.方法名() ).thenReturn( 自定义结果 ),还提供了其他用法让我们使用。
此时在执行覆盖率的时候发现都是为零,排查问题发现是之前写的案列还在加载,修改后的单侧没有被加载上。 解决办法只选最外层,但这样的问题就是看覆盖率具体的话,需要一层一层点击进去。
thenReturn 系列方法
当使用任何整数值调用 userService 的 getUser() 方法时,就回传一个名字为 I'm mock3 的 User 对象。
Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user的名字也为I'm mock
限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象。
Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user为null
当调用 userService 的 insertUser() 方法时,不管传进来的 user 是什么,都回传 100。
Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn(100);
Integer i = userService.insertUser(new User()); //会返回100
thenThrow 系列方法
当调用 userService 的 getUserById() 时的参数是 9 时,抛出一个 RuntimeException。
Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
User user = userService.getUserById(9); //会抛出一个RuntimeExceptio
如果方法没有返回值的话(即是方法定义为 public void myMethod() {...}),要改用 doThrow() 抛出 Exception。
Mockito.doThrow(new RuntimeException("mock throw exception")).when(userService).print();
userService.print(); //会抛出一个RuntimeException
verify 系列方法
检查调用 userService 的 getUserById()、且参数为3的次数是否为1次。
Mockito.verify(userService, Mockito.times(1)).getUserById(Mockito.eq(3)) ;
验证调用顺序,验证 userService 是否先调用 getUserById() 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用insertUser() 方法。
InOrder inOrder = Mockito.inOrder(userService);
inOrder.verify(userService).getUserById(3);
inOrder.verify(userService).getUserById(5);
inOrder.verify(userService).insertUser(Mockito.any(User.class));
mock 打桩
// 对所有注解了@Mock的对象进行模拟
// MockitoAnnotations.initMocks(this);
// 不使用注解,可以对单个对象进行 mock
UserService user = new UserService();
// 构造被测试对象
UserService spy = Mockito.spy(user);
// 打桩,构建当 spy的 getOne 函数执行参数为 1的时候,设置返回的结果 User
Mockito.when(spy.getOne(1L)).thenReturn(new User("1","1", "I'm mock 3"));
// 打桩,构建当 spy的 getOne 函数执行参数为 2的时候,设置返回的结果 null
Mockito.when(spy.getOne(2L)).thenReturn(null);
// 打桩,构建当 spy的 getOne 函数执行参数为 3的时候,设置结果抛出异常
Mockito.when(spy.getOne(3L)).thenThrow(new IllegalArgumentException("The id is not support"));
// 打桩,当 spy.updateUser 执行任何User类型的参数,返回的结果都是true
Mockito.when(spy.updateUser(Mockito.any(User.class))).thenReturn(true);
mock 验证
// 没有经过 mock 的 updateUser 方法,它返回的是 false
//userService = Mockito.spy(UserService.class); // 经过
boolean updated = userService.updateUsername("x");
Assert.assertThat(updated, Matchers.not(true));
Mockito.verify(userService).updateUsername("x");
// 验证调用两次getUniqueId
verify(test, times(2)).updateUsername("x");
// 也可以使用下面的方法来替代调用的次数
verify(test, never()).someMethod("never called 从来没有调用");
verify(test, atLeastOnce()).someMethod("called at least once 至少被调用一次");
verify(test, atLeast(2)).someMethod("called at least twice 至少被调用5次");
verify(test, times(5)).someMethod("called five times 被调用5次");
verify(test, atMost(3)).someMethod("called at most 3 times 至多被调用3次");
//下面的方法用来检查是否所有的用例都涵盖了,如果没有将测试失败
//放在所有的测试后面
verifyNoMoreInteractions(test);
mock其他实列
// 返回多个值的示例
@Test
public void testMoreThanOneReturnValue() {
Iterator i= mock(Iterator.class);
when(i.next()).thenReturn("Mockito").thenReturn("rocks");
String result= i.next()+" "+i.next();
//assert
assertEquals("Mockito rocks", result);
}
// 如何根据输入来返回值
@Test
public void testReturnValueDependentOnMethodParameter() {
Comparable c= mock(Comparable.class);
when(c.compareTo("Mockito")).thenReturn(1);
when(c.compareTo("Eclipse")).thenReturn(2);
//assert
assertEquals(1, c.compareTo("Mockito"));
}
// 返回值独立于输入值
@Test
public void testReturnValueInDependentOnMethodParameter() {
Comparable c= mock(Comparable.class);
when(c.compareTo(anyInt())).thenReturn(-1);
//assert
assertEquals(-1, c.compareTo(9));
}
// 根据提供参数的类型返回特定的值
@Test
public void testReturnValueInDependentOnMethodParameter2() {
Comparable c= mock(Comparable.class);
when(c.compareTo(isA(Todo.class))).thenReturn(0);
//assert
assertEquals(0, c.compareTo(new Todo(1)));
}
模拟对象有两种方式:
MockitoAnnotations.initMocks(this);
xxx= Mockito.mock(xxx.class);
/*对void的方法设置模拟*/
Mockito.doAnswer(invocationOnMock -> {
System.out.println("进入了Mock");
return null;
}).when(fileRecordDao).insert(Mockito.any());
当 Mockito 监视一个真实的对象的时候,我们也可以模拟这个对象的方法返回我们设置的期望值,
List spy = spy(new LinkedList());
List spy = spy(new LinkedList());
// IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);