在实际的单元测试中,我们测试的类之间会有或多或少的耦合,导致我们无法顺利的进行测试,这时我们就可以使用Mockito,Mockito库能够Mock对象,替换我们原先依赖的真实对象,这样我们就可以避免外部的影响,只测试本类,得到更准确的结果。
如下示例:
从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks
mock引入方式
在app的build.gradle文件中添加依赖
dependencies {
testCompile "org.mockito:mockito-core:2.11.0"
}
4种方式mock对象
在Test代码中使用Mockito
普通方式
直接使用mock方法
public class MockTest1 {
@Test
public void testPersonIsNotNull(){
Person person = mock(Person.class); //使用方法1:直接使用mock方法
assertNotNull(person);
}
@Test
public void sampleTest2(){
//直接mock出一个接口对象
MVPContract.Presenter mockPresenter = mock(MVPContract.Presenter.class);
when(mockPresenter.getUserName()).thenReturn("qingmei2");
String userName = mockPresenter.getUserName();
}
}
使用注解
public class MockTest1 {
//方式2:使用@Mock注解
@Mock
Person mPerson;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);//进行初始化
}
@Test
public void testIsNotNull(){
assertNotNull(mPerson);
}
}
MockitoRule方式
//使用MockitoRule
public class MockTest3 {
//使用@Mock注解
@Mock
Person mPerson;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testIsNotNull(){
assertNotNull(mPerson);
}
}
使用运行器方式
//使用运行器方式
@RunWith(MockitoJUnitRunner.class)//使用MockitoJUnitRunner
public class MockTest2 {
//使用@Mock注解
@Mock
Person mPerson;
@Test
public void testIsNotNull(){
assertNotNull(mPerson);
}
}
常用的打桩方式
因为Mock出的对象中非void方法都将返回默认值,比如int方法将返回0,对象方法将返回null等,而void方法将什么都不做。“打桩”顾名思义就是将我们Mock出的对象进行操作,比如提供模拟的返回值等,给Mock打基础。
方法名 | 方法描述 |
---|---|
thenReturn(T value) | 设置要返回的值 |
thenThrow(Throwable… throwables) | 设置要抛出的异常 |
thenAnswer(Answer<?> answer) | 对结果进行拦截 |
doReturn(Object toBeReturned) | 提前设置要返回的值 |
doThrow(Throwable… toBeThrown) | 提前设置要抛出的异常 |
doAnswer(Answer answer) | 提前对结果进行拦截 |
doCallRealMethod() | 调用某一个方法的真实实现 |
doNothing() | 设置void方法什么也不做 |
测试用例1
/使用MockitoRule
public class MockTest4 {
//使用@Mock注解
@Mock
Person mPerson;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testPersonReturn(){
//直接调用函数 返回的结果是对应类型的默认值,int默认是0
System.out.println(mPerson.getName());
System.out.println(mPerson.getAge());
//打桩
when(mPerson.getName()).thenReturn("张三");
when(mPerson.getAge()).thenReturn(30);
// when(mPerson.getAge()).thenThrow(new NullPointerException());
System.out.println(mPerson.getName());
System.out.println(mPerson.getAge());
//另外一种打桩方式
doReturn("小小").when(mPerson).getName();
System.out.println(mPerson.getName());
}
}
运行结果如下:
测试用例2:
/使用MockitoRule
public class MockTest4 {
//使用@Mock注解
@Mock
Person mPerson;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testPersonThenAnswer(){
when(mPerson.eat(anyString())).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
//参数获取
return args[0].toString();
}
});
System.out.println(mPerson.eat("面条"));
}
}
测试结果如下:
用例3:
//方法连续调用测试
@Test
public void testPerson(){
when(mPerson.getAge())
.thenReturn(10)
.thenThrow(new RuntimeException("方法调用第二次抛出异常"))
.thenReturn(12);
int age1 = mPerson.getAge();
int age2;
try {
age2 = mPerson.getAge();
}catch (Exception e){
age2 = -1;
}
int age3 = mPerson.getAge();
System.out.println("age1: "+age1);
System.out.println("age2: "+age2);
System.out.println("age3: "+age3);
//另外一种方式
when(mPerson.getName()).thenReturn("张三", "李四", "王五");
System.out.println("name1: "+mPerson.getName());
System.out.println("name2: "+mPerson.getName());
System.out.println("name3: "+mPerson.getName());
}
测试结果如下:
参数匹配器
anyObject() | 匹配任何对象 |
---|---|
anyObject() | 匹配任何对象 |
any(Class type) | 与anyObject()一样 |
any() | 与anyObject()一样 |
anyBoolean() | 匹配任何boolean和非空Boolean |
anyByte() | 匹配任何byte和非空Byte |
anyCollection() | 匹配任何非空Collection |
anyDouble() | 匹配任何double和非空Double |
anyFloat() | 匹配任何float和非空Float |
anyInt() | 匹配任何int和非空Integer |
anyList() | 匹配任何非空List |
anyLong() | 匹配任何long和非空Long |
anyMap() | 匹配任何非空Map |
anyString() | 匹配任何非空String |
contains(String substring) | 参数包含给定的substring字符串 |
argThat(ArgumentMatcher matcher) | 创建自定义的参数匹配模式 |
示例代码:
//使用MockitoRule
public class MockTest6 {
//使用@Mock注解
@Mock
Person mPerson;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testPersonVerify1(){
//吃任何东西是,都返回米饭
when(mPerson.eat(anyString())).thenReturn("米饭");
//输出米饭
System.out.println(mPerson.eat("面条"));
}
@Test
public void testPersonVerify2(){
//吃的东西包括“米”字是,返回米饭
when(mPerson.eat(contains("米"))).thenReturn("米饭");
//输出null
System.out.println(mPerson.eat("面条"));
}
@Test
public void testPersonVerify3(){
//自定义输入字符长度为偶数时,输出面条。
when(mPerson.eat(argThat(new ArgumentMatcher<String>() {
@Override
public boolean matches(String argument) {
return argument.startsWith("面");
}
}))).thenReturn("面条");
//输出面条
System.out.println(mPerson.eat("面夸海口"));
}
}
验证方法的调用次数
Mockito提供verify关键字来实现校验方法是否被调用,具体调用如下例子:
测试用例1:
@Test
public void testVerify(){
when(mPerson.getAge()).thenReturn(30);
//延时1s验证
System.out.println(mPerson.getAge());
System.out.println(System.currentTimeMillis());
verify(mPerson,after(1000)).getAge();
System.out.println(System.currentTimeMillis());
}
因为getAge方法在之前调用过,所以使用verify进行校验时,可以正常通过,结果如下:
测试用例2:
@Test
public void testVerify(){
when(mPerson.getAge()).thenReturn(30);
//延时1s验证
System.out.println(mPerson.getAge());
System.out.println(System.currentTimeMillis());
verify(mPerson,after(1000)).getName();
System.out.println(System.currentTimeMillis());
}
因为getName方法在之前没有调用过,所以使用verify进行校验时,用例通不过,结果如下:
方法名 | 方法描述 |
---|---|
after(long millis) | 在给定的时间后进行验证 |
timeout(long millis) | 验证方法执行是否超时 |
atLeast(int minNumberOfInvocations) | 至少进行n次验证 |
atMost(int maxNumberOfInvocations) | 至多进行n次验证 |
description(String description) | 验证失败时输出的内容 |
times(int wantedNumberOfInvocations) | 验证调用方法的次数 |
never() | 验证交互没有发生,相当于times(0) |
after(long millis) | 在给定的时间后进行验证 |
only() | 验证方法只被调用一次,相当于times(1) |
public class MockTest5 {
//使用@Mock注解
@Mock
Person mPerson;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testVerify1(){
when(mPerson.getAge()).thenReturn(30);
System.out.println(mPerson.getAge());
System.out.println(System.currentTimeMillis());
//延时1s验证
verify(mPerson,after(1000)).getName();
System.out.println(System.currentTimeMillis());
}
@Test
public void testPersonVerifyAtLeast(){
when(mPerson.getAge()).thenReturn(30);
mPerson.getAge();
//验证getAge方式是否至少被调用2次
verify(mPerson,atLeast(2)).getAge();
}
@Test
public void testPersonVerify2Times(){
mPerson.getAge();
mPerson.getAge();
//验证getAge方法是否被调用了2次
verify(mPerson,times(2)).getAge();
}
@Test
public void testPersonVerify2(){
mPerson.getAge();
mPerson.getAge();
//验证getAge方法是否在100ms内被调用了2次
verify(mPerson,timeout(100).times(2)).getAge();
}
}
校验方法执行顺序
@Test
public void testPersonVerifyOrder1(){
List singleMock = mock(List.class);
singleMock.add("first add");
singleMock.add("second add");
InOrder inOrder = inOrder(singleMock);
//inOrder保证了方法的顺序执行要和上面的顺序一致
//顺序一致,用例通过
inOrder.verify(singleMock).add("first add");
inOrder.verify(singleMock).add("second add");
//顺序不一致,用例不能通过
// inOrder.verify(singleMock).add("second add");
// inOrder.verify(singleMock).add("first add");
}
@Test
public void testPersonVerifyOrder2(){
List firstMock = mock(List.class);
List secondMock = mock(List.class);
firstMock.add("first add");
secondMock.add("second add");
InOrder inOrder = inOrder(firstMock, secondMock);
//下列代码会确认是否firstmock优先secondMock执行add方法(和先前调用add方法的顺序保持一致)
inOrder.verify(firstMock).add("first add");
inOrder.verify(secondMock).add("second add");
}
抛出异常
@Test
public void testPersonVerifyThrow(){
List mockList = mock(List.class);
doThrow(new NullPointerException("testPersonVerifyThrow抛出空指针异常")).when(mockList).clear();
doThrow(new IllegalAccessException("testPersonVerifyThrow抛出参数异常")).when(mockList).contains(anyInt());
mockList.add("string");//这个不会抛出异常
mockList.add(12);//抛出了异常,因为参数是Int
mockList.clear();//抛出空指针异常
}