• 使用 mockito 进行单元测试


    博客主页:JavaProfessional
    一个专注于Java的博主,致力于使用通俗易懂的语句讲解内容,期待您的关注!

    前言

    当前Spring是Java的一大杀器,但是因为所有依赖关系都已经被Spring解决掉了,就导致我们在写完Spring程序后想写单元测试却无从下手。而且现在ORM框架为我们解决了数据库问题,导致我们的程序离开了数据库之后完全不能执行,想写单元测试更是难上加难。

    依赖

      基本操作

      模拟任意类或接口

      当你模拟任何类得到一个对象时,该对象将获取该类对象的所有操作。

      
      import java.util.List;
      
      import static org.mockito.Mockito.mock;
      
      @Slf4j
      public class AppNoticeServiceTest {
          @Test
          public void baseOperation() {
              List mockedList = mock(List.class);
              boolean addSuccess = mockedList.add(1);
              if(addSuccess) {
                  // 程序略
                  log.info("add执行成功");
              } else {
                  // 程序略
                  log.info("add执行失败");
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      上述我们模拟了一个List接口。

      返回模拟的数据——插桩stubbing

      思考一下,上一个例子当中,我可以把mockedList的第一个数据打印出来吗?按照我们之前的逻辑当然可以,我不是有一个add的操作吗?但是仔细想想,我们模拟的可是List接口啊,都没有指明实现类,难道mockito已经智能到这种程度了吗?当然不是,其实mockito对象只是具备了类的方法,但是调用方法并不会产生真实的数据,我们必须告诉mockito对象执行某种操作后,给我一个什么数据。比如上述代码中,我们执行add操作后,给我返回一个truefalse,好让我控制之后的程序执行路径。

      package com.bootdo.app.service.impl;
      
      import lombok.extern.slf4j.Slf4j;
      import org.junit.Test;
      
      import java.util.List;
      
      import static org.mockito.Mockito.mock;
      import static org.mockito.Mockito.when;
      
      @Slf4j
      public class AppNoticeServiceTest {
          @Test
          public void baseOperation() {
              List mockedList = mock(List.class);
              when(mockedList.add(1)).thenReturn(Boolean.FALSE);
              boolean addSuccess = mockedList.add(1);
              if(addSuccess) {
                  // 程序略
                  log.info("add执行成功");
              } else {
                  // 程序略
                  log.info("add执行失败");
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      此段代码我们去除了add操作,因为add操作并不是我们真实的目的,真实目的是我们想要控制程序的执行走的路径,达到全覆盖。

      验证

      验证某一方法是否执行

      我们如何验证程序按照我们想要的路径争取执行了呢?可以使用verifyMockito verify方法用于检查是否发生了某些行为,我们可以在测试方法代码的末尾使用Mockito验证方法,以确保调用了指定的方法。

      package com.bootdo.app.service.impl;
      
      import lombok.extern.slf4j.Slf4j;
      import org.junit.Test;
      
      import java.util.List;
      
      import static org.mockito.Mockito.*;
      
      @Slf4j
      public class AppNoticeServiceTest {
          @Test
          public void baseOperation() {
              List mockedList = mock(List.class);
              when(mockedList.get(1)).thenReturn("one");
              String content =(String) mockedList.get(1);
              if(content.equals("one")) {
                  // 程序略
                  mockedList.add("two");
              } else {
                  // 程序略
                  mockedList.add("three");
              }
              verify(mockedList).add("two");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      我们成功验证了mockedList执行了add("two")方法,证明走了true分支。

      验证某一个方法执行次数

       mockedList.add("three times");
       mockedList.add("three times");
       mockedList.add("three times");
       verify(mockedList, times(3)).add("three times");
      
      • 1
      • 2
      • 3
      • 4

      验证执行顺序

       // A. Single mock whose methods must be invoked in a particular order
       List singleMock = mock(List.class);
      
       //using a single mock
       singleMock.add("was added first");
       singleMock.add("was added second");
      
       //create an inOrder verifier for a single mock
       InOrder inOrder = inOrder(singleMock);
      
       //following will make sure that add is first called with "was added first", then with "was added second"
       inOrder.verify(singleMock).add("was added first");
       inOrder.verify(singleMock).add("was added second");
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      两个类的执行顺序

       // B. Multiple mocks that must be used in a particular order
       List firstMock = mock(List.class);
       List secondMock = mock(List.class);
      
       //using mocks
       firstMock.add("was called first");
       secondMock.add("was called second");
      
       //create inOrder object passing any mocks that need to be verified in order
       InOrder inOrder = inOrder(firstMock, secondMock);
      
       //following will make sure that firstMock was called before secondMock
       inOrder.verify(firstMock).add("was called first");
       inOrder.verify(secondMock).add("was called second");
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      验证类的所有模拟方法是否都被验证了

      这个方法很难理解,而且网上的资料大多数都是直译官网,非常难以理解。以下我们通过一个例子来理解一下:

      public class Test {
          public String method1(){return "method1";}
          public String method2(){return "method2";}
      }
      public class MockTest {
          public static void main(String[] args) {
              Test test = mock(Test.class);
              test.method1();
              verifyNoMoreInteractions(test);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      以上调用了test.method1()但是却没有验证该结果,所以会报错。

      public class MockTest {
          public static void main(String[] args) {
              Test test = mock(Test.class);
              test.method1();
              verify(test).method1();
              verifyNoMoreInteractions(test);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      以上的程序虽然仅仅掉了test的method1方法,没有调用test的method2方法,但是却在调用method1之后,进行了验证了执行次数(默认为1),所以verifyNoMoreInteractions就不会报错了

      参数匹配

      如果一个方法需要填写参数,但是输入什么参数并不是很重要,重要的是他的返回值,那么你可以使用参数匹配。

      when(mockedList.get(anyInt())).thenReturn("element");
      
      • 1

      你甚至可以模拟空指针和任意类

      when(mock.dryRun(isNull())).thenReturn("state");
      when(mock.dryRun(any(String.class))).thenReturn("state");
      
      • 1
      • 2

      模拟异常

      doThrow(new RuntimeException()).when(mockedList).clear();
      // 以下程序执行会抛出异常
      mockedList.clear();
      
      • 1
      • 2
      • 3

      参考资料

      官方文档

      好文推荐

      1. 通俗易懂JVM
      2. 为什么Spring中的IOC(控制反转)能够降低耦合性(解耦)?
      3. 效率翻倍,这些idea的逆天功能你知道吗?
    • 相关阅读:
      数据化管理洞悉零售及电子商务运营——销售中的数据化管理
      Flask vs. Django:选择适合你的Web开发框架【第134篇—Flask vs. Django】
      90、00后严选出的数据可视化工具:奥威BI工具
      【2024校招】2023-9-17 度小满信贷系统平台部后端一面
      npm ERR! code ERESOLVE错误解决
      Scala基本语法
      RK3588 开启HDCP
      如何使用HTML制作个人网站( web期末大作业)
      基于C51小车测速
      NodeJS & Dapr Javascript SDK 官方使用指南
    • 原文地址:https://blog.csdn.net/tinpo_123/article/details/125492046