Mockito库能够Mock对象、验证结果以及打桩(stubbing)。比如在测试时,可以用mockito模拟查询数据库的操作,即将查询数据库的方法拦截,并人工设置其返回值,这样就不用真正去数据库中拿取数据了。此外还可以对某个方法的运行结果进行验证等。
1. 相关包和依赖的导入
在使用mockito之前首先要导入包,建议采用静态导入:import static org.mockito.Mockito.*;
,此外,还要在pom.xml文件中导入Junit相关依赖。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
2. 验证方法是否执行
@Test
public void mockTest() {
List mockedList = mock(List.class);
mockedList.add(1);
// 验证是否执行了add()方法
verify(mockedList).add(1);
mockedList.clear();
// 验证是否执行了clear()方法
verify(mockedList).clear();
}
如果所验证的方法执行了,则不输出任何内容,反之,如果没有执行则会输出报错信息。
3. 测试桩
@Test
public void mockTest() {
List mockedList = mock(List.class);
// 测试桩,当指定的语句执行后返回对应的值或抛异常
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 输出测试桩对应的内容first
System.out.println(mockedList.get(0));
// 输出测试桩对应的内容,抛异常
System.out.println(mockedList.get(1));
}
其输出为 first
和 RuntimeExceptioni类型的异常信息。
基于注解的方式
基于注解的方式可以让代码更加简介,也是最为推荐的。在使用基于注解的mock时,需要先让代码支持mock,有三种方式:
-
在测试类上添加
@RunWith(MockitoJUnitRunner.class)
注解 -
在测试类里添加方法,并让其在所有测试方法执行前执行
// @Before注解表示在任意使用@Test注解标注的public void方法执行之前执行 @Before public void init() { // 这句代码需要在运行测试函数之前被调用 //MockitoAnnotations.openMocks(this); // 下面这条语句和上面的效果相同 MockitoAnnotations.initMocks(this); }
-
在测试类中添加
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
以上三种任选一种即可,建议用第一种。
下面给出完整的测试代码:
package cn.ecnu;
import java.util.List;
import cn.ecnu.domain.User;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.MockitoRule;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
/*
// @Before注解表示在任意使用@Test注解标注的public void方法执行之前执行
@Before
public void init() {
// 这句代码需要在运行测试函数之前被调用
//MockitoAnnotations.openMocks(this);
// 下面这条语句和上面的效果相同
MockitoAnnotations.initMocks(this);
}*/
/*@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();*/
// 只能作用于全局变量
@Mock
List mockedList;
@Test
public void mockTest() {
// 测试桩,当指定的语句执行后返回对应的值或抛异常
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 输出测试桩对应的内容first
System.out.println(mockedList.get(0));
// 输出测试桩对应的内容,抛异常
System.out.println(mockedList.get(1));
}
}
其输出和上面的代码一样
参数匹配器
@Test
public void mockTest() {
List mockedList = mock(List.class);
// isA()的参数为一个类型,可以是任意类型。当调用的方法的参数为该类型时,该测试桩将会起效
when(mockedList.get(isA(Integer.class))).thenReturn("isA");
System.out.println(mockedList.get(0));
// any()和isA()的作用完全一样
when(mockedList.get(any(Integer.class))).thenReturn("any");
System.out.println(mockedList.get(1));
// 当参数为Int类型(Interger也算)时,返回对应的值
when(mockedList.get(anyInt())).thenReturn("anyInt");
// 当参数等于1时,返回对应的值
when(mockedList.get(eq(1))).thenReturn("equal");
// 输出测试桩对应的内容first
System.out.println(mockedList.get(0));
System.out.println("----------");
// 输出测试桩对应的内容,抛异常
System.out.println(mockedList.get(1));
}
以上代码的输出为:
isA
any
anyInt
----------
equal
而当将第15行注释后,输出为:
isA
any
anyInt
----------
anyInt
猜测是更准确的参数匹配器会覆盖不准确的,而不是同时起效。
当然自带的参数匹配器还有anyString()、anyList(),、anyCollection()等,不再赘述。
验证方法的调用次数
@Test
public void mockTest() {
List mockedList = mock(List.class);
mockedList.add(1);
mockedList.add(1);
// 验证是否执行了恰好2次
verify(mockedList,times(2)).add(1);
// 验证是否执行了至少2次
verify(mockedList,atLeast(2)).add(1);
// 验证是否执行了至多2次
verify(mockedList,atMost(2)).add(1);
// 验证是否从未被执行
verify(mockedList, never()).add(1);
// 默认验证是否执行了恰好1次
verify(mockedList).add(1);
}
如果验证通过,则不输出任何内容,反之输出对应的错误信息。
无返回值的方法执行时抛异常
@Test
public void mockTest() {
List mockedList = mock(List.class);
// 无返回值的方法执行时抛异常
doThrow(new RuntimeException()).when(mockedList).clear();
mockedList.clear();
}
验证是否按指定顺序执行
验证单个mock对象的执行顺序
@Test
public void mockTest() {
List mockedList = mock(List.class);
mockedList.add(1);
mockedList.add(2);
mockedList.add(3);
// 为mock对象创建一个InOrder对象
InOrder inOrder = inOrder(mockedList);
// 验证是否按顺序执行
inOrder.verify(mockedList).add(1);
inOrder.verify(mockedList).add(2);
inOrder.verify(mockedList).add(3);
}
当按指定顺序执行时不输出任何内容,反之输出错误信息。需要注意的是只要执行序列中出现了指定的这个序列就验证通过。比如,添加顺序为“1 3 2 3”或“1 2 3 4”都是验证通过的。
验证多个mock对象的执行顺序
@Test
public void mockTest() {
List firstMock = mock(List.class);
List secondMock = mock(List.class);
firstMock.add(1);
secondMock.add(2);
// 为mock对象创建一个InOrder对象
InOrder inOrder = inOrder(firstMock,secondMock);
// 验证是否按顺序执行
inOrder.verify(firstMock).add(1);
inOrder.verify(secondMock).add(2);
}
使用方法类似,不再赘述。
为连续的调用做测试桩
@Test
public void mockTest() {
List mockedList = mock(List.class);
when(mockedList.get(0))
// 第一次调用时返回firstReturn
.thenReturn("firstReturn")
// 第二次调用时返回secondReturn
.thenReturn("secondReturn")
// 第三次(包括)之后的调用抛异常
.thenThrow(new RuntimeException());
mockedList.add(1);
mockedList.add(2);
// 第一次调用
System.out.println(mockedList.get(0));
// 第二次调用
System.out.println(mockedList.get(0));
// 非测试桩的调用
System.out.println(mockedList.get(1));
// 第三次调用
System.out.println(mockedList.get(0));
}
上述代码的输出为:
firstReturn
secondReturn
null
java.lang.RuntimeException
......
一种更简洁的版本是把测试桩改成:
when(mockedList.get(0))
.thenReturn("firstReturn", "secondReturn")
.thenThrow(new RuntimeException());
其输出和之前一样。
调用原方法执行
class User{
public int method(){
return 222;
}
}
@Test
public void mockTest(){
User mockedUser = mock(User.class);
when(mockedUser.method()).thenReturn(111);
// 返回mock的结果111
System.out.println(mockedUser.method());
when(mockedUser.method()).thenCallRealMethod();
// 调用原函数,返回222
System.out.println(mockedUser.method());
}
监控真实对象
spy和mock是相反功能。
spy:如果不对spy对象的methodA打桩,那么调用spy对象的methodA时,会调用真实方法。
mock:如果不对mock对象的methodA打桩,将doNothing,且返回默认值(null,0,false)。
@Test
public void mockTest() {
// 这里使用List.class会有问题
List spyList = spy(ArrayList.class);
when(spyList.size()).thenReturn(10086);
// 效果和上个语句一样,类似的还有doThrow/doAnser等
doReturn(10086).when(spyList).size();
spyList.add(5);
// 返回第一个元素5
System.out.println(spyList.get(0));
// 返回打桩的值10086
System.out.println(spyList.size());
}
注解版:
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
@Spy
List spyList = new ArrayList();
// 如果是 List spyList; 则会有问题
@Test
public void mockTest() {
when(spyList.size()).thenReturn(10086);
// 效果和上个语句一样,类似的还有doThrow/doAnser等
doReturn(10086).when(spyList).size();
spyList.add(5);
// 返回第一个元素5
System.out.println(spyList.get(0));
// 返回打桩的值10086
System.out.println(spyList.size());
}
}
超时验证
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
@Mock
List mockedList;
@Test
public void mockTest() throws InterruptedException {
mockedList.add(1);
mockedList.add(1);
// 验证在过去的100ms中是否执行了1次
verify(mockedList,timeout(100)).add(1);
// 验证在过去的100ms中是否执行了恰好2次
verify(mockedList,timeout(100).times(2)).add(1);
// 验证在过去的100ms中是否执行了至少2次
verify(mockedList,timeout(100).atLeast(2)).add(1);
}
}