Mockito 是一个针对 Java 的单元测试模拟框架,是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。在有这些模拟框架之前,为了编写某一个函数的单元测试,程序员必须进行十分繁琐的初始化工作,以保证被测试函数中使用到的环境变量以及其他模块的接口能返回预期的值,有些时候为了单元测试的可行性,甚至需要牺牲被测代码本身的结构。单元测试模拟框架则极大的简化了单元测试的编写过程:在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
@Test
public void verify_behaviour() {
System.out.println("验证某些行为");
// 模拟创建一个List对象
List mock = mock(List.class);
// 使用mock的对象
mock.add(1);
mock.clear();
// 验证add(1)和clear()行为是否发生
verify(mock).clear();
verify(mock).add(1);
}
@Test
public void verify_stub() {
System.out.println("如何做测试桩");
// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
// 测试桩
when(mockedList.get(0)).thenReturn("first");
// 输出“first”
System.out.println(mockedList.get(0));
// 因为get(999) 没有打桩,因此输出null
System.out.println(mockedList.get(999));
// 验证get(0)被调用的次数
verify(mockedList).get(0);
}
@Test
public void verify_matcher() {
System.out.println("参数匹配器");
// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
// 使用内置的anyInt()参数匹配器
when(mockedList.get(anyInt())).thenReturn("element");
// 使用自定义的参数匹配器( 在isValid()函数中返回你自己的匹配器实现 )
when(mockedList.contains(argThat(new IsValid()))).thenReturn(true);
// 输出element
System.out.println(mockedList.get(999));
// 输出true
System.out.println(mockedList.contains(1));
// 你也可以验证参数匹配器
verify(mockedList).get(anyInt());
}
private class IsValid extends ArgumentMatcher<List>{
@Override
public boolean matches(Object o) {
return (int)o == 1 || (int)o == 2;
}
}
@Test
public void verify_times() {
System.out.println("验证函数的确切、最少、从未调用次数");
LinkedList mockedList = mock(LinkedList.class);
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// 验证具体的执行次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 使用never()进行验证,never相当于times(0)
verify(mockedList, never()).add("never happened");
// 使用atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");
}
@Test
public void verify_exception() {
System.out.println("为返回值为void的函数通过Stub抛出异常");
LinkedList mockedList = mock(LinkedList.class);
doThrow(new RuntimeException()).when(mockedList).clear();
// 调用这句代码会抛出异常
mockedList.clear();
}
@Test
public void verify_order() {
System.out.println("验证执行顺序");
// A. 验证mock一个对象的函数执行顺序
List singleMock = mock(List.class);
singleMock.add("was added first");
singleMock.add("was added second");
// 为该mock对象创建一个inOrder对象
InOrder inOrder = inOrder(singleMock);
// 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. 验证多个mock对象的函数执行顺序
List firstMock = mock(List.class);
List secondMock = mock(List.class);
firstMock.add("was called first");
secondMock.add("was called second");
// 为这两个Mock对象创建inOrder对象
InOrder inOrder2 = inOrder(firstMock, secondMock);
// 验证它们的执行顺序
inOrder2.verify(firstMock).add("was called first");
inOrder2.verify(secondMock).add("was called second");
}
@Test
public void verify_interaction() {
System.out.println("确保交互操作不会执行在mock对象上");
List mockOne = mock(List.class);
List mockTwo = mock(List.class);
List mockThree = mock(List.class);
mockOne.add("one");
verify(mockOne).add("one");
verify(mockOne,never()).add("two");
//验证零互动行为
verifyZeroInteractions(mockTwo,mockThree);
}
@Test
public void verify_redundant() {
System.out.println("找出冗余的互动(即未被验证到的)");
LinkedList mockedList = mock(LinkedList.class);
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
// 下面的验证将会失败
verifyNoMoreInteractions(mockedList);
}
@Mock
private List mockList;
@Test
public void verify_shorthand() {
System.out.println("使用注解来快速模拟对象");
//必须初始化,要不然mockList的对象为NULL
MockitoAnnotations.initMocks(this);
mockList.add(1);
verify(mockList).add(1);
}
@Mock
private List mockList;
@Test(expected = RuntimeException.class)
public void verify_consecutive(){
System.out.println("为连续的调用做测试桩");
//模拟连续调用返回期望值,如果分开,则只有最后一个有效
when(mockList.get(0)).thenReturn(0);
when(mockList.get(0)).thenReturn(1);
when(mockList.get(0)).thenReturn(2);
when(mockList.get(1)).thenReturn(0).thenReturn(1).thenThrow(new RuntimeException());
assertEquals(2,mockList.get(0));
assertEquals(2,mockList.get(0));
assertEquals(0,mockList.get(1));
assertEquals(1,mockList.get(1));
//第三次或更多调用都会抛出异常
mockList.get(1);
}
@Mock
private List mockList;
@Test(expected = RuntimeException.class)
public void verify_callback() {
System.out.println("为回调做测试桩");
// 使用Answer来生成我们我们期望的返回
when(mockList.get(anyInt())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return "hello world:" + args[0];
}
});
assertEquals("hello world:0", mockList.get(0));
assertEquals("hello world:999", mockList.get(999));
}
@Test
public void verify_spy(){
System.out.println("监控真实对象");
List list = new LinkedList();
List spy = spy(list);
// 你可以为某些函数打桩
when(spy.size()).thenReturn(100);
// 通过spy对象调用真实对象的函数
spy.add("one");
spy.add("two");
// 输出第一个元素
System.out.println(spy.get(0));
// 因为size()函数被打桩了,因此这里返回的是100
System.out.println(spy.size());
// 交互验证
verify(spy).add("one");
verify(spy).add("two");
}
@Test
public void verify_unstubbed(){
System.out.println("修改没有测试桩的调用的默认返回值");
//mock对象使用Answer来对没有测试桩的调用返回默认期望值
List mock = mock(List.class,new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return 999;
}
});
//下面的get(1)没有预设,通常情况下会返回NULL,但是使用了Answer改变了默认期望值
assertEquals(999, mock.get(1));
//下面的size()没有预设,通常情况下会返回0,但是使用了Answer改变了默认期望值
assertEquals(999,mock.size());
}
@Test
public void verify_capturing() {
System.out.println("为下一步的断言捕获参数");
PersonDao personDao = mock(PersonDao.class);
PersonService personService = new PersonService(personDao);
ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
personService.update(1, "jack");
verify(personDao).update(argument.capture());
assertEquals(1, argument.getValue().getId());
assertEquals("jack", argument.getValue().getName());
}
class Person {
private int id;
private String name;
Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
interface PersonDao {
public void update(Person person);
}
class PersonService {
private PersonDao personDao;
PersonService(PersonDao personDao) {
this.personDao = personDao;
}
public void update(int id, String name) {
personDao.update(new Person(id, name));
}
}
@Test
public void verify_reset() {
System.out.println("重置mocks对象");
List list = mock(List.class);
when(list.size()).thenReturn(10);
list.add(1);
assertEquals(10, list.size());
// 重置mock,清除所有的互动和预设
reset(list);
assertEquals(0, list.size());
}