• Junit+Mockito 快速测试你的程序


    什么是mockito

    是什么

    • 是一个java语言下的一单元测试框架

    使用场景

    1. 在资源环境不完善的情况下也可进行单元测试,确保开发进度与程序的正确性
    • 在整个项目中通常依赖多个部分组成如:数据库、缓存数据库、第三方系统等等。。
    • 在团队并发开发过程中,总不可能每个每个依赖资源都是准备好的,所以有没有办法缺点这些组件的情况下也可以进行部分代码的单元测试呢?这里使用mockit+junit test 可以解决

    概念

    mock对象

    • 通过反射技术创建出来的一个代理对象;
    • 这个对象可模拟真实场景去执行预期的程序与结果

    打桩

    • 可以简单的理解为为其设置期待值如:
    when(mockLinkedList.get(0)).thenReturn("first")
    // 理解:将每一个元素设置为"first" 在需要的使用每0个元素时就返回值为“first”
    
    • 1
    • 2

    验证

    • 验证是否有执行过、或者执行了几次;mock 对象创建后就会监控对mock对象执行的每一个操作
    verify(mockLinkedList).get(0)
    // 理解:验证get(0)被调用的次数
    
    • 1
    • 2

    常用注解

    • @Mock: 自动注入一个代理对象
    • @Spy:自动注入一个真实对象
    • @InjectMocks:声明为一个需要依赖其他对象,并自动将带有@Mock 的对象注入到应该对象中

    创建mock对象的三种方式

    版本方式一方式二方式三
    Junit4@RunWith(MockitoJunitRunner.class)Mockito.mock(xx.class)/Mockito.spy(xx.class)MockitoAnnotations.openMocks(class)+@Mock注解
    Junit5@ExtendWith(MockitoExtension.class)Mockito.mock(xx.class)/Mockito.spy(xx.class)MockitoAnnotations.openMocks(class)+@Mock注解

    插桩的三种方式:

    doReturn(返回值).when(类句柄).doXX();
    使用场景:
    • 对于spy 调用时并不会调用一次目标方法,而是直接返回指定值。
    • 真实调用方法时,有其他依赖方法调用了数据库、缓存、消息等,如果你想跳过这些调用则可以使用此方法来跳过这些调用;
    // Setup
    final List<DriverBo> expectedResult =
    Arrays.asList(
    DriverBo.builder()
    .id(0L)
    .contactInformations(Arrays.asList(DriverContactInformationBo.builder().build()))
    .build());
    
    // 调用queryList方法时,直接返回指定的数据
    Mockito.doReturn(Arrays.asList(Convert.toList(expectedResult)))
    .when(driverServiceImplUnderTest)
    .queryList(Arrays.asList(0L));
    
    // Run the test
    final List<DriverBo> result = driverServiceImplUnderTest.queryList(Arrays.asList(0L));
    
    // Verify the results
    assertThat(result.size()).isEqualTo(expectedResult.size());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    when(类句柄).thenReturn(返回值);
    使用场景:
    • 对于spy 调用时会先调用一次目标方法。
    doNothing().when(类句柄).doXX();
    使用场景:
    • 处理返回值为null 的方法。

    插入桩抛出异常的三种方式:

    • doThrow(RuntimeException.class.when(list.get(4)); 这种方式适用于没有返回值和spy 对象;
    • when(list.get(4)).thenThrow(RuntimeException.class); 这种方式适用于有返回值和mock对象;

    调用真实方法

    // 使用mockito对象时,真实调用目标方法
    Mockito.when(xxService.method(arg))
            .thenCallRealMethod();
    
    • 1
    • 2
    • 3

    指定插桩逻辑

    // 自定义插桩逻辑,invocation可以拿到对应的调用方法,mock对象,入参等信息
    Mockito.doAnswer(
      new Answer() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
          Object argument = invocation.getArgument(0);
          return 1;
        }
      })
      .when(driverServiceImplUnderTest)
      .queryById(0L);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    简单入门

    示例

    • 示例一:
    @Test
    public void list() {
      // mock creation 创建mock对象
      List mockedList = mock(List.class);
    
      // using mock object 使用mock对象
      mockedList.add("one");
      mockedList.clear();
    
      // verification 验证
      verify(mockedList).add("one");
      verify(mockedList).clear();
    }
    
    @Test
    public void linkedList() {
      LinkedList mockLinkedList = mock(LinkedList.class);
      RuntimeException runtimeException = new RuntimeException();
      // 打桩测试
      when(mockLinkedList.get(0)).thenReturn("first");
      when(mockLinkedList.get(1)).thenReturn(runtimeException);
    
      // 验证结果
      Assertions.assertEquals("first", mockLinkedList.get(0));
      Assertions.assertEquals(runtimeException, mockLinkedList.get(1));
    
      // 验证
      verify(mockLinkedList, times(1)).get(1);
    }
    
    • 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
    • 27
    • 28
    • 29

    实战案例

    案例一:CRUD操作

    UserDao
    public class UserDao {
      public User queryByName(String name) {
        System.out.println(name);
        return User.builder().id(5).name(name).build();
      }
    
      public Integer save(User user) {
        return 1;
      }
    
      public void update(User user) {}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    User
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class User {
      private Integer id;
      private String name;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    UserService
    public class UserService {
    
      private UserDao userDao = new UserDao();
    
      /**
       * 新增一个用户
       *
       * @param name
       * @return
       */
      public Integer save(String name) throws Exception {
        if (StrUtil.isBlank(name)) {
          throw new ValidationException("名称不能为空");
        }
        User user = userDao.queryByName(name);
        if (user != null) {
          throw new Exception("用户已存在,请不要重复添加");
        }
        Integer id = userDao.save(user);
        return id;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    UserServiceTest
    
    
    class UserServiceTest {
    
      @Mock private UserDao userDao;
    
      @InjectMocks @Spy private UserService userService;
    
      @BeforeEach
      void before() {
        MockitoAnnotations.openMocks(this);
      }
    
      @Test
      void save() throws Exception {
        String name = null;
    
        // 名字为空
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof ValidationException);
        }
    
        // 设置值走正常流程
        name = "gzwen";
    
        // 正常流程
        Mockito.doReturn(null).when(userDao).queryByName(name);
        Mockito.when(userService.save(name)).thenReturn(1);
        Integer newUserId = userService.save(name);
        Assertions.assertEquals(1, newUserId);
    
        //  用户已存在,不允许重新插入,抛出异常
        Mockito.doReturn(new User()).when(userDao).queryByName(name);
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof Exception);
        }
    
        //  用户已存在,不允许重新插入,抛出异常;输入任意参数都返回一个空user 对象
        Mockito.doReturn(new User()).when(userDao).queryByName(Mockito.any());
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof Exception);
        }
      }
    }
    
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    案例二:mock 静态方法

    添加信赖
            <dependency>
                <groupId>org.mockitogroupId>
                <artifactId>mockito-inlineartifactId>
              	<version>4.3.1version>
                <scope>testscope>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    静态方法:StaticUtils
    public class StaticUtils {
      public StaticUtils() {}
    
      public static List<Integer> range(int tart, int end) {
        return IntStream.range(tart, end).boxed().collect(Collectors.toList());
      }
    
      public static String name() {
        return "Echo";
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    单元测试实例:StaticUtilsTest
    
    @ExtendWith(MockitoExtension.class)
    class UserServiceTest {
    
      @Mock private UserDao userDao;
    
      @InjectMocks @Spy private UserService userService;
    
      @Test
      void save() throws Exception {
        String name = null;
    
        // 名字为空
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof ValidationException);
        }
    
        // 设置值走正常流程
        name = "gzwen";
    
        // 正常流程
        Mockito.doReturn(null).when(userDao).queryByName(name);
        Mockito.when(userService.save(name)).thenReturn(1);
        Integer newUserId = userService.save(name);
        Assertions.assertEquals(1, newUserId);
    
        //  用户已存在,不允许重新插入,抛出异常
        Mockito.doReturn(new User()).when(userDao).queryByName(name);
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof Exception);
        }
    
        //  用户已存在,不允许重新插入,抛出异常;输入任意参数都返回一个空user 对象
        Mockito.doReturn(new User()).when(userDao).queryByName(Mockito.any());
        try {
          userService.save(name);
          Assertions.fail("这里会挂");
        } catch (Exception e) {
          Assertions.assertTrue(e instanceof Exception);
        }
      }
    }
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    其他:

    如果你访问github网站比较慢,或者一些资源无法访问你可以看一下这里:网络加速器

    关于我

    • 关注不迷路,点赞走一波~ 转载请标注~
    • 公众号
      在这里插入图片描述
  • 相关阅读:
    30.7.5 忘记root密码的解决方案
    06.特殊CSS伪选择器
    Unity Shader 溶解效果
    计算机毕业设计Java幼儿健康管理系统(系统+程序+mysql数据库+Lw文档)
    STM32Cube高效开发教程<基础篇>(八)----通用定时器-输入捕获、输出比较、PWM输出/互补输出等
    系统架构设计高级技能 · 构件与中间件技术
    如何在线免费caj转Word
    CSS文本超限后使用省略号代替
    openwrt篇 路由器上写cname-01
    Thrift快速入门和简单示例
  • 原文地址:https://blog.csdn.net/gu_zhang_w/article/details/133914390