当进行单元测试时候,有些对象的创建十分复杂或者说很耗费时间。这个时候,可以使用Mockito框架,模拟一个对象的创建。
本示例参考书籍
Android开发进阶,从小工到专家,加上笔者对demo的理解
首先分析下mock对象与普通的对象有什么不同
Test
public void testCreateMockObject() {
//创建mock对象
List mockList=Mockito.mock(List.class);
//调用list的接口函数方法
mockList.add("one");
System.out.println("size:"+mockList.size()); //size=0
mockList.clear();
System.out.println("size:"+mockList.size()); //size=0
}
代码很简单,mock一个List对象,调用List的add()方法以及clear()方法,打印下List的size的变化
如果是普通的List的话,很显然size应该为1和0,但是使用mock对象打印出来的是0和0
也就是说,mock的对象相当于为每个方法提供了一个空的实现,并没有实际的功能
前面的模拟对象,只是简单的提供了方法的空的实现,既然我们要模拟一个对象的方法调用,自然需要能模拟返回值,可以通过thenReturn模拟返回值,通过thenThrow模拟异常的抛出
@Test
public void testReturnValue() {
List mockList = Mockito.mock(List.class);
//此时没有打桩,返回null
System.out.println(mockList.get(0));
//模拟get方法返回值
Mockito.when(mockList.get(0)).thenReturn("hello");
Mockito.when(mockList.get(1)).thenThrow(new IndexOutOfBoundsException());
//打桩了返回hello
System.out.println(mockList.get(0));
//此处会抛出异常
System.out.println(mockList.get(1));
}
简单的设置get(0), get(1)那还好,但是如果要设置100个数字呢,或者所有的get呢,这个时候可以使用参数匹配器
@Test
public void testMatcher() {
List mockList = mock(List.class);
when(mockList.get(anyInt())).thenReturn("thanks");
//参数匹配器,也就是list只包含"thanks"一个参数
when(mockList.contains(argThat(new ArgumentMatcher(){
@Override
public boolean matches(Object o) {
if("thanks".equals(o)){
return true;
}
return false;
}
}))).thenReturn(true);
System.out.println(mockList.get(100)); //thanks
System.out.println(mockList.contains("thanks")); //true
System.out.println(mockList.contains("hello")); //false
}
注意当某个方法使用参数匹配器时候,所有的参数都需要使用参数匹配器
有的时候,我们需要验证方法调用了多少次
@Test
public void testMethodTimes() {
List mockList = mock(List.class);
mockList.add(1);
//mockList.add(1);
mockList.add(2);
mockList.add(2);
mockList.add(3);
//此处判断 add(1)调用了几次,必须方法名一样,参数名一样
//如果此处不一致则会抛出异常
verify(mockList).add(1);
verify(mockList,times(1)).add(1); //和上面的一样的
verify(mockList,times(2)).add(2);
verify(mockList,never()).add(4); //验证方法从未被调用过
verify(mockList, atLeastOnce()).add(1); //至少调用一次
verify(mockList, atLeast(2)).add(2);//至少调用两次
verify(mockList, atMost(3)).add(3);//至多调用3次
}
@Test
public void testNotCalled(){
List mockList = mock(List.class);
mockList.get(0);
//只要调用过方法就会抛出异常
verifyZeroInteractions(mockList);
}
有些对象需要在同一个单元测试的各个方法中重复创建,此时我们可以使用注解来创建对象
@Mock
private List mMockList;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
@Test
public void testCreateMockObjWithAnnotation() {
when(mMockList.get(0)).thenReturn(1);
System.out.println(mMockList.get(0));
}
有时候我们需要一个方法调用第一次时候返回值和调用第二次返回值不一样
@Test
public void testContinuationCalled() {
when(mMockList.remove(0)).thenReturn(1).thenThrow(new RuntimeException());
System.out.println(mMockList.remove(0));
System.out.println(mMockList.remove(0));
//另一种方式,第一次调用返回1,第二次返回2,第三次返回3
when(mMockList.remove(1)).thenReturn(1,2,3);
}
有时候,当方法调用时候我们不仅想控制方法的返回值,还希望能控制方法的内部流程
@Test
public void testAnswer() {
when(mMockList.get(anyInt())).thenAnswer(new Answer
其实这个和thenReturn,thenThrow差不大多
@Test
public void testAction() {
doThrow(new RuntimeException()).when(mMockList).clear();
mMockList.clear();
//这样就不行,因为mMockList.clear()方法返回void
//when(mMockList.clear()).thenThrow(new RuntimeException());
}
还有类似的doReturn(),doThrow(),doAnswer(),doCallRealMethod()一系列的方法
当出现以下情况时候必须使用这种模式
1)测试void函数
2)在受监控的对象上测试函数
3)不止一次地测试同一个函数,在测试过程中改变Mock对象的行为
这一部分测试时候总是报错,不知道原因,因此没有测试