Android单元测试(二):Mockito框架的使用

本文详细介绍了如何在Android单元测试中使用Mockito框架,包括mock对象的四种引入方式:普通方式、注解方式、MockitoRule和运行器方式。通过具体的测试用例展示了如何设置返回值、参数匹配器、验证方法调用次数以及检查方法执行顺序。此外,还阐述了Mockito在处理异常情况的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在实际的单元测试中,我们测试的类之间会有或多或少的耦合,导致我们无法顺利的进行测试,这时我们就可以使用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();//抛出空指针异常
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值