zoukankan      html  css  js  c++  java
  • Mockito使用总结

    Mockito使用总结

    写UT时,经常会遇到执行过程中调用的方法返回结果不可控的情况,为了专注在某个确定范围内的开发自测,需要模拟这些方法和类的行为,Mockito提供了很好的解决方案。

    使用Mockito可以很方便的设置、校验方法或类的行为,但是前提是首先创建一个mock对象,才能基于Mockito进行操作。

    创建一个mock对象

    可以简单的调用mock方法来创建一个mock对象:

    List mockedList = Mockito.mock(List.class)
    

    或者更简单地,可以直接用@mock:

    @Mock
    List mockedList;
    

    如果要使用注解,必须打开注解开关:

    MockitoAnnotations.initMocks(this);
    

    设置mock对象的行为

    我们可以设置mock对象调用某个方法的返回值:

    Mockito.when(mockedList.get(0)).thenReturn("val");
    

    或者设置调用方法时抛出某个异常:

    Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException());
    

    对于一个mock对象,没有设置过的方法行为均返回null:

    mockedList.get(999) // 将返回null
    

    在实际使用中常常设置某个方法的返回值为另一个mock对象,在复杂的情况时可以以此来控制整个测试过程。

    验证mock对象的行为

    1、验证mock对象是否调用过某个方法:

    Mockito.verify(mockedList).add("one");  // 是否执行过mockedList.add("one")
    

    注意验证的目标对象必须是mock的,否则会报错。

    2、验证方法调用了多少次、是否从未调用过:

    Mockito.verify(mockedList, Mockito.times(2)).add("one");  // 是否调用过两次add("one")
    Mockito.verify(mockedList, Mockito.never()).add("one");  // 是否从未调用过add("one")
    

    3、验证某个mock对象是否从未使用过:

    Mockito.verifyZeroInteractions(mockedList);
    

    4、验证同一个mock对象执行不同方法的先后顺序:

    List singleMock = Mockito.mock(List.class);
    singleMock.add("first");
    singleMock.add("second");
    InOrder inOrder = Mockito.inOrder(singleMock);
    // 验证调用顺序
    inOrder.verify(singleMock).add("first");
    inOrder.verify(singleMock).add("second");
    

    5、验证不同mock对象执行不同方法的先后顺序:

    List firstMock = Mockito.mock(List.class);
    List secondMock = Mockito.mock(List.class);
    firstMock.add("first");
    secondMock.add("second");
    InOrder inOrder = Mockito.inOrder(firstMock,secondMock);
    // 验证调用顺序
    inOrder.verify(firstMock).add("first");
    inOrder.verify(secondMock).add("second");
    

    匹配器

    匹配器可以代替一类参数:

    1、验证一类行为:

    // 验证是否执行过mockedList的get方法,参数为任意int数
    Mockito.verify(mockedList).get(Mock.anyInt()); 
    

    2、设置一类行为:

    // 当mockedList调用get方法,参数为任意int数,返回值都是element
    Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("element");
    

    部分模拟

    1、部分模拟的概念

    前面提到,如果某个对象被mock,那么它将不再拥有原本的功能,全部的方法都被替换掉了,只会返回设置好的值,如果mock对象的某个方法没有设置,那么就会返回null。当我们想要验证某个对象的行为,同时又需要它一部分功能保持不变时,就要使用部分模拟:

    // 使用部分模拟需要用spy方法,它必须是某个已经建立好的对象的封装,不能用class对象为构造参数
    List list = new LinkedList();
    List mockedList = Mockito.spy(list);
    

    后续我们可以正常调用它的方法,模拟它的部分行为,并验证它的功能:

    // 设置method1方法的行为
    Mockito.when(mockedList.method1(Mockito.anyInt())).thenReturn("element");
    
    // 调用method2将正常返回原本该返回的值,走正常的方法流程,与mock无关
    mockedList.method2();
    
    // 验证对象的行为
    Mockito.verify(mockedList).method1(Mock.anyInt()); 
    

    2、不可模拟情况的替代方案

    一旦对象是部分模拟的,在设置行为的过程中就可能发生异常,比如对于一个空集合mockedList,当执行下列语句时就会直接抛出越界异常:

    Mockito.when(mockedList.get(0)).thenReturn("foo");
    

    这种情况下应该用这种形式来模拟行为:

    Mockito.doReturn("foo").when(mockedList).get(0);
    

    3、使用mock来部分模拟

    部分模拟也可以不用spy方法,使用mock一样可以完成相同的功能:

    List mockedList = Mockito.mock(List.class);
    
    // 调用某个方法时返回真实值
    Mockito.when(mockedList.someMethod()).thenCallRealMethod();
    

    4、注解的使用

    和mock一样,spy方法也可以方便的用注解来代替,此时也要注意必须使用初始化后的对象,同时开启注解:

    @Spy
    List list = new LinkedList();
    

    5、使用部分模拟来完成对局部变量的替换

    假设我们要测试这样一段代码:

    Request request = new Request();
    
    // 设置request的属性
    ...
    
    // 调用方法
    Response response = serviceInvoker.invoke(SERVICE_NAME, request);
    

    如果我们想模拟invoke方法的行为,很难准确的确定request这个参数,即使创建一个相同的对象,设置其行为:

    // 创建request对象并设置其属性
    Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
    

    这样也是行不通的,因为在待测试的代码中,request是一个局部变量,它的地址和我们创建出来的对象地址是不同的,也就无法准确的进行模拟。

    这种情况解决方案是在待测试代码中将创建request的方法抽取出来:

    Request request = getRequest();
    
    // 调用方法
    Response response = serviceInvoker.invoke(SERVICE_NAME, request);
    

    然后在UT中,首先mock这个方法getRequest,使其返回值是我们控制好的request对象,然后再进行测试即可,这样就能将我们预期的request对象放入方法中了:

    // 创建request对象并设置其属性
    Request request = new Request();
    ...
    
    // 模拟getRequest方法的行为
    Mockito.when(instance.getRequest()).thenReturn(request);
    
    // 完成我们一开始想要的行为定义
    Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
    

    @InjectMocks

    日常开发过程中,另外一个常用的注解是@InjectMocks,用它标注的类会自动装配其中已经被@Mock和@Spy标注的字段:

    // ModelDaoImpl类中有一个字段mediator, 这里自动装配其中的mediator字段
    @InjectMocks
    ModelDaoImpl modelDao; 
    
    @Mock
    Mediator mediator;
    
    ...{
        // 这里调用的create是真实的方法,这里面如果有mediator调用某个方法,就可以通过自动装配事先设置它的行为了
        Mockito.when(mediator.get(0)).thenReturn("foo");
        modelDao.create();
        ...
    }
    

    PowerMock

    PowerMock可以来配合Mockito使用,模拟静态方法的行为。

    1、使用前的准备

    需要在测试类上加注解:

    @RunWith(PowerMockRunner.class) // 一个固定的启动类
    @PrepareForTest({Factory.class}) // 设置要模拟的静态方法对应的类
    

    2、设置静态方法、或者新建对象的行为:

    PowerMockito.mockStatic(Factory.class); // 用spyStatic可以部分模拟,在每次设置行为前都要执行该方法
    PowerMockito.when(Factory.getLogger()).thenReturn(logger); // 设置Factory.getLogger()方法返回值
    
    PowerMockito.doThrow(new RunTimeException()).when(Factory.class) // 执行返回值为void的方法时抛出异常
    
    PowerMockito.whenNew(MyClass.class).withNoArguments().thenThrow(new IOEeception()); // 新建对象时抛出异常
    

    3、验证静态方法、或者新建对象的行为:

    // 检查getLogger是否调用了2次
    PowerMockito.verifyStatic(Mockito.times(2));
    Factory.getLogger();
    
    // 检查getLogger是否调用了1次
    PowerMockito.verifyStatic(); // default times is once
    Factory.getLogger();
    
    // 检查getLogger是否从未被调用
    PowerMockito.verifyStatic(Mockito.never());
    Factory.getLogger();
    
    // 是否新建过MyClass类的对象
    PowerMockito.verifyNew(MyClass.class).withNoArguments();
    

    4、私有方法的模拟与验证:

    // classUnderTest调用私有方法methodName,参数为parameter时,返回值为value
    PowerMockito.doReturn(value).when(classUnderTest, "methodName", "parameter");
    
    // classUnderTest是否调用两次私有方法methodName,参数为parameter
    PowerMockito.verifyPrivate(classUnderTest, Mockito.times(2)).invoke("methodName", "parameter");
    
  • 相关阅读:
    利用vector声明二维数组及获取行数和列数
    链表翻转
    关于一段测试代码的知识点补习
    初探二叉搜索树
    排序系列之——再叙堆排序
    Top k 问题
    哈希表及其冲突与排解
    进程互斥与同步
    基础算法之几种排序算法
    进程的描述、状态及控制
  • 原文地址:https://www.cnblogs.com/yinyunmoyi/p/14203794.html
Copyright © 2011-2022 走看看