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

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

  • 什么是Mock
  • 在软件开发的世界之外, "Mock"一词是指模仿或者效仿。 因此可以将“Mock”理解为一个替身、替代者,在软件开发中提及"Mock",通常理解为模拟对象或者Fake

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

  • Mock的好处是什么
  • 提前创建测试,提高代码质量、TDD(测试驱动开发)

    创建一个验证或者演示程序,为无法访问的资源编写测试【关注尚硅谷,轻松学IT】

    什么是Mockito

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

    1、引入依赖:

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.3.3</version>
        <scope>test</scope>
    </dependency>

    注意:Mockito 3.X的版本使用了JDK8的API,但与2.X的版本并没有太大的变化。

    2、在测试类中添加@RunWith注解,并制定Runner的类,即MockitoJUnitRunner

    @RunWith(MockitoJUnitRunner.class)
    public class MockitoDemoTest {
        //注入依赖的资源对象
        @Mock
        private MockitoTestService mockitoTestService;
        @Before
        public void before(){
            MockitoAnnotations.initMocks(this);
    

    从代码中可观察到,使用@Mock标识对象是被Mock的,同时在初始化前置执行MockitoAnnotations.initMocks(this)告诉框架生Mock相关注解生效。

    3、验证对象的行为Verify

    @Test
    public void testVerify(){
        //创建mock
        List mockedList = mock(List.class);
        mockedList.add("1");
        mockedList.clear();
        //验证list调用过add的操作行为
        verify(mockedList).add("1");
        //验证list调用过clear的操作行为
        verify(mockedList).clear();
        //使用内建anyInt()参数匹配器,并存根
        when(mockedList.get(anyInt())).thenReturn("element");
        System.out.println(mockedList.get(2)); //此处输出为element
        verify(mockedList).get(anyInt());
    

    4、存根—stubbing

    stubbing完全是模拟一个外部依赖,用来提供测试时所需要的测试数据

    @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();
    
  • 存根(stub)可以覆盖:例如测试方法可以覆盖通用存
  • 一旦做了存根方法将总是返回存根的值,无论这个方法被调用多少次
  • 5、存根的连续调用

    @Test
    public void testStub() {
        when(mock.someMethod("some arg"))
        .thenThrow(new RuntimeException())
        .thenReturn("foo");
        mock.someMethod("some arg"); //第一次调用:抛出运行时异常
        //第二次调用: 打印 "foo"
        System.out.println(mock.someMethod("some arg"));
        //任何连续调用: 还是打印 "foo" (最后的存根生效).
        System.out.println(mock.someMethod("some arg"));
        //可供选择的连续存根的更短版本:
        when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
        when(mock.someMethod(anyString())).thenAnswer(new Answer() {
            Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                Object mock = invocation.getMock();
                return "called with arguments: " + args;
        // "called with arguments: foo
        System.out.println(mock.someMethod("foo"));
    

    6、参数匹配器

    @Test
    public void testArugument{
        //使用内建anyInt()参数匹配器
        when(mockedList.get(anyInt())).thenReturn("element");
        System.out.println(mockedList.get(999)); //打印 "element"
        //同样可以用参数匹配器做验证
        verify(mockedList).get(anyInt());
        //注意:如果使用参数匹配器,所有的参数都必须通过匹配器提供。
        verify(mock)
        .someMethod(anyInt(), anyString(), eq("third argument"));
        //上面是正确的 - eq(0也是参数匹配器),而下面的是错误的
        verify(mock)
        .someMethod(anyInt(), anyString(), "third argument");
    

    7、验证精确调用次数/至少X次/从不

    @Test
    public void testVerify{
        List<String> mockedList = new ArrayList();
        mockedList.add("once");
        mockedList.add("twice");
        mockedList.add("twice");
        mockedList.add("three times");
        mockedList.add("three times");
        mockedList.add("three times");
        //下面两个验证是等同的 - 默认使用times(1)
        verify(mockedList).add("once");
        verify(mockedList, times(1)).add("once");
        verify(mockedList, times(2)).add("twice");
        verify(mockedList, times(3)).add("three times");
        //使用using never()来验证. never()相当于 times(0)
        verify(mockedList, never()).add("never happened");
        //使用 atLeast()/atMost()来验证
        verify(mockedList, atLeastOnce()).add("three times");
        verify(mockedList, atLeast(2)).add("five times");
        verify(mockedList, atMost(5)).add("three times");
    

    8、验证调用顺序

    @Test
    public void testOrder(){
        // A. 单个Mock,方法必须以特定顺序调用
        List singleMock = mock(List.class);
        //使用单个Mock
        singleMock.add("was added first");
        singleMock.add("was added second");
        //为singleMock创建 inOrder 检验器
        InOrder inOrder = inOrder(singleMock);
        //确保add方法第一次调用是用"was added first",然后是用"was added second"
        inOrder.verify(singleMock).add("was added first");
        inOrder.verify(singleMock).add("was added second");
    来源作者:GoQeng

    广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员