Java模拟框架Mockito简单使用

日常的开发中,习惯性地写完需求代码后,嗖的一声运行一个main函数或写几个简单的JUnit的单元测试来跑功能点,多写几个单元测试过没有问题就可以上线了(其实这样是不规范的),对于需要对接第三方或者验证不同条件的代码分支逻辑时,这种方法就会变得不可取,因为业务逻辑中需要依赖其他的接口,而这时候所依赖的接口还没有准备好,那应该怎么办呢?

这时候该Mockito派上用场了,一方面使用Mockito可以屏蔽依赖接口并返回Mock数据,使得双方的开发得以同步进行(确定接口的协议)编码,另一方面使用Mockito验证业务逻辑,当日后更改到某处代码即可回归测试用例看改动是否覆盖到所有的测试点,因此使用Mockito不单单能保证代码的质量,更能提高代码维护性、提前发现代码的bug。

img

Mock四要素

  • 什么是Mock

  • 为什么需要Mock

Mock是为了解决units、代码分层开发之间由于耦合而难于被测试的问题,所以mock object是单元测试的一部分

  • Mock的好处是什么

提前创建测试,提高代码质量、TDD(测试驱动开发)

  • 并行工作

创建一个验证或者演示程序,为无法访问的资源编写测试

什么是Mockito

Mockito是一个非常优秀的模拟框架,可以使用它简洁的API来编写漂亮的测试代码,它的测试代码可读性高同时会产生清晰的错误日志。

使用Mockito

Mockito官网

1、引入依赖

        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>4.0.0</version>
            <scope>test</scope>
        </dependency>

2、示例代码

import com.xxx.common.redis.service.RedisService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.util.LinkedList;
import java.util.List;

/**
 * @version 1.0
 * @date 2022/5/26 9:58
 * @since : JDK 11
 */
@RunWith(MockitoJUnitRunner.class)
public class MockTest extends Mockito {

    // 模拟对象
    @Mock
    private RedisService redisService;

    // 验证对象行为Verify
    @Test
    public void testVerify() {
        // 可以mock接口
        List mock = mock(List.class);
        mock.add("1");
        mock.clear();
        // 验证mock对象前面已经调用过add操作
        verify(mock).add("1");
        // 验证mock调用过clear操作
        verify(mock).clear();
        // 使用内建anyInt()参数匹配器,并存根
        when(mock.get(anyInt())).thenReturn("element");
        System.out.println(mock.get(2));
        // 此处输出为element
        verify(mock).get(anyInt());
    }

    // 存根—stubbing
    // stubbing完全是模拟一个外部依赖,用来提供测试时所需要的测试数据
    // 存根(stub)可以覆盖:例如测试方法可以覆盖通用存,一旦做了存根方法将总是返回存根的值,无论这个方法被调用多少次
    @Test
    public void testStub() {
        // 可以mock具体的类,而不仅仅是接口
        LinkedList mockedList = mock(LinkedList.class);
        // 存根(stubbing)
        when(mockedList.get(0)).thenReturn("first");
        when(mockedList.get(1)).thenThrow(new RuntimeException());
        // 下面会打印 "first"
        System.out.println(mockedList.get(0));
        // 下面会抛出运行时异常
        System.out.println(mockedList.get(1));
        // 下面会打印"null" 因为get(999)没有存根(stub)
        System.out.println(mockedList.get(999));
        doThrow(new RuntimeException()).when(mockedList).clear();
        // 下面会抛出 RuntimeException:
        mockedList.clear();
    }

    // 存根的连续调用
    @Test
    public void testStub1() {
        LinkedList mockedList = mock(LinkedList.class);
        when(mockedList.get(0)).thenThrow(new RuntimeException()).thenReturn("这是返回值");
        try {
            //第一次调用:抛出运行时异常
            mockedList.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第二次调用: 打印 "foo"
        System.out.println(mockedList.get(0));
        // 任何连续调用: 还是打印 "foo" (最后的存根生效).
        System.out.println(mockedList.get(0));
        // 可供选择的连续存根的更短版本:
        when(mockedList.get(0)).thenReturn(1, 2, 3, 4, 5, 6, 7, 8, 9);
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        when(mockedList.get(anyInt())).thenAnswer(new Answer() {
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                Object mock = invocation.getMock();
                return "当调用时返回调用方法的第一个参数:" + args[0];
            }
        });
        // 当调用时返回调用方法的第一个参数:1
        System.out.println(mockedList.get(1));

    }

    // 参数匹配
    @Test
    public void testArugument() {
        LinkedList mockedList = mock(LinkedList.class);
        //使用内建anyInt()参数匹配器
        when(mockedList.get(anyInt())).thenReturn("element");
        // 打印 "element"
        System.out.println(mockedList.get(999));
        // 同样可以用参数匹配器做验证
        verify(mockedList).get(anyInt());
        // 注意:如果使用参数匹配器,所有的参数都必须通过匹配器提供。
        // 正确 使用参数匹配器匹配参数
        verify(mockedList).get(anyInt());
        // 正确 使用了已经产生交互的模拟数据
        verify(mockedList).get(999);
        // 验证之前mockedList是否执行过get(0)的操作
        verify(mockedList).get(0);
    }

    // 验证方法精确调用次数/至少X次/从不
    @Test
    public void testVerify1() {
        List mockedList = mock(List.class);
        mockedList.add(1);
        mockedList.add(2);
        mockedList.add(2);
        mockedList.add(3);
        mockedList.add(3);
        mockedList.add(3);
        //下面两个验证是等同的 - 默认使用times(1)
        verify(mockedList).add(1);
        verify(mockedList, times(1)).add(1);
        verify(mockedList, times(2)).add(2);
        verify(mockedList, times(3)).add(3);
        // 使用never()来验证从未执行过的操作,never()相当于times(0)
        verify(mockedList, never()).add("never happened");

        // 使用 atLeast()/atMost()来验证
        // 最后一次执行的add(3)
        verify(mockedList, atLeastOnce()).add(3);
        // 最少执行2次add(2)
        verify(mockedList, atLeast(2)).add(2);
        // 最多执行5次add(2)
        verify(mockedList, atMost(5)).add(2);
    }

    // 验证调用顺序
    // 单个Mock,方法必须以特定顺序调用
    @Test
    public void testOrder() {
        List mockedList = mock(List.class);
        mockedList.add("was added first");
        mockedList.add("was added second");
        // 为singleMock创建 inOrder 检验器
        InOrder inOrder = inOrder(mockedList);
        // 确保add方法第一次调用是用"was added first",然后是用"was added
        // second"
        inOrder.verify(mockedList).add("was added first");
        inOrder.verify(mockedList).add("was added second");
    }
}
posted @ 2022-05-26 13:36  黄河大道东  阅读(152)  评论(0编辑  收藏  举报