Java-测试-Mockito 入门篇

news2024/9/20 22:07:45

之前很长一段时间我都认为测试就是使用SpringBootTest类似下面的写法:

@SpringBootTest
class SysAuthServiceTest {
    @Autowired
    SysRoleAuthMapper sysRoleAuthMapper;

    @Test
    public void test() {
        QueryWrapper<SysRoleAuth> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_id", 1);
        List<SysRoleAuth> sysRoleAuths = sysRoleAuthMapper.selectList(queryWrapper);
        assertNotNull(sysRoleAuths);
        assert sysRoleAuths.size() > 0;
    }
}

这样也确实是单元测试,只不过这个只是其中的一方面。如果你的某个方法并不依赖于Spring容器,也需要启动整个Spirng环境吗?启动这个环境可能是比较耗时间的。本文对Mockito框架做一个初步探索,找找写单测的感觉。

单元测试规范

https://alibaba.github.io/p3c/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95.html

依赖

本文基于 Junit5 + Mockito 3, 高版本的Mockito 需要高版本的Java支持。比如你使用Mockito 5就需要满足Java版本在11以上。

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

HelloWord - 不需要Spring

在很多时候我们的单元测试是不需Spring的,下面我们来看一个例子。

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class SysRoleServiceTest {
    @Mock
    RedisHelper redisHelper;
    @BeforeEach
    void setUp() {
        when(redisHelper.get(anyString())).thenAnswer((Answer<SysRole>) invocation -> {
            String key = invocation.getArgument(0);
            if ("1836664638416211969".equals(key)) {
                return createRole("admin", "管理员");
            } else {
                return createRole("user", "普通用户");
            }
        });

        when(redisHelper.get(anyString(), anyLong())).thenAnswer((Answer<SysRole>) invocation -> {
            // 通过invocation.getArgument() 获取参数
            String key = invocation.getArgument(0);
            Long id = invocation.getArgument(1);
            if ("1836664638416211969".equals(key) && id == 10L) {
                return createRole("admin", "管理员");
            } else {
                return createRole("user", "普通用户");
            }
        });
    }

    private SysRole createRole(String roleName, String description) {
        SysRole role = new SysRole();
        role.setRoleName(roleName);
        role.setDescription(description);
        role.setCreateTime(new Date());
        role.setUpdateTime(new Date());
        role.setStatus(1);
        role.setLogicDelete(0);
        return role;
    }

    @Test
    void test() {
        String key = "1836664638416211969";
        Object o1 = redisHelper.get(key, 100);
        Object o2 = redisHelper.get(key);
        System.out.println(o1);
        System.out.println(o2);
        assertNotEquals(o1, o2);
    }

}

在这个例子中我们定义了一个RedisHelper,模拟了两个重载方法。为什么要加@MockitoSettings(strictness = Strictness.LENIENT)呢?
如果不加会出现下面的错误,这是因为比如我们模拟了两个方法,但是其中一个方法我们并没有使用:
在这里插入图片描述
这个错误有两个解决方法:1) 你加上前文中的注解 2)你模拟的方法都被使用了,就不会有这个错误

HelloWord - 需要Spring

在一些时候比如我们就是基于Spring环境来写单测,但比如Redis组件还在安装中又或者当前的Redis不可用,我们可以暂时模拟。通过 @MockBean的方式。与上面的区别是:1)我们用的注解不一样 @Mock 和 @MockBean 2)我们需要加SpringBootTest注解

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@SpringBootTest
class SysRoleServiceTest {
    @MockBean
    RedisHelper redisHelper;
    @BeforeEach
    void setUp() {
        when(redisHelper.get(anyString())).thenAnswer((Answer<SysRole>) invocation -> {
            String key = invocation.getArgument(0);
            if ("1836664638416211969".equals(key)) {
                return createRole("admin", "管理员");
            } else {
                return createRole("user", "普通用户");
            }
        });

        when(redisHelper.get(anyString(), anyLong())).thenAnswer((Answer<SysRole>) invocation -> {
            // 通过invocation.getArgument() 获取参数
            String key = invocation.getArgument(0);
            Long id = invocation.getArgument(1);
            if ("1836664638416211969".equals(key) && id == 1L) {
                return createRole("admin", "管理员");
            } else {
                return createRole("user", "普通用户");
            }
        });
    }

    private SysRole createRole(String roleName, String description) {
        SysRole role = new SysRole();
        role.setRoleName(roleName);
        role.setDescription(description);
        role.setCreateTime(new Date());
        role.setUpdateTime(new Date());
        role.setStatus(1);
        role.setLogicDelete(0);
        return role;
    }

    @Test
    void test() {
        String key = "1836664638416211969";
        Object o1 = redisHelper.get(key, 100);
        Object o2 = redisHelper.get(key);
        System.out.println(o1);
        System.out.println(o2);
        assertNotEquals(o1, o2);
    }
}

Mockito 的用法

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

模拟对象
  • 模拟行为
List mockedList = mock(List.class);
mockedList.add("one");
verify(mockedList).add("one");
mockedList.stream().forEach(System.out::println);
// 可以指定模拟策略
MockDemo mock = mock(MockDemo.class, Mockito.RETURNS_SMART_NULLS);

这里我模拟一个List对象,特别要注意的是这个对象不存储数据所以后面的流式打印是不会有任何输出的。这个List的对象只在模拟调用方法的这个行为。比如verify(mockedList).add(“one”);就是用来确认这个方法是不是被调用了一次,而前面我们确实调用了一次add方法。那这个有什么用呢?这个可以再不影响数据的前提下测试我们是不是正确的调用了对象的方法,有没有多次调用或者调用次数符不符合预期。
VerificationModeFactory给我们提供了一些验证模型,比如至少多少次atLeast,当然我们是可以拓展自己的规则的。
在这里插入图片描述

  • 模拟对象的方法并指定行为
    我们可以模拟一个对象的方法,并且指定这个方法调用以后的行为,比如返回一个值,抛出一个异常。
LinkedList mockedList = mock(LinkedList.class);
// 返回一个值
when(mockedList.get(0)).thenReturn("first");
// 抛出一个异常
when(mockedList.get(1)).thenThrow(new RuntimeException());
参数匹配

当然我们的参数可以使用通用匹配:
在这里插入图片描述
上面的例子中其实有用到,这里再举个例子,当我们试图从LinkedList 中获取SysRole对象的时候,我们对信息进行处理以后返回:

@Test
public void test() {
    Map mockedList = mock(HashMap.class);
    when(mockedList.get(any(SysRole.class))).thenAnswer(invocation -> {
        SysRole role = invocation.getArgument(0);
        if (role.getRoleName().equals("test")) {
            role.setStatus(1);
            role.setLogicDelete(1);
            role.setDescription("handled");
        }
        return role;
    });
    SysRole sysRole = new SysRole();
    sysRole.setRoleName("test");
    Object o = mockedList.get(sysRole);
    System.out.println(o);
}

又例如当添加的参数是字符串1时添加成功:

List mockedList = mock(List.class);
when(mockedList.add(argThat(str -> str.equals("1")))).thenReturn(true);
Object o = mockedList.add("2");
System.out.println(o);

又比如满足第一个参数是int, 第二个参数是字符串,第三个参数是third argument返回yes

MockDemo demo = mock(MockDemo.class);
when(demo.doSomething(anyInt(), anyString(), eq("third argument"))).thenReturn("yes");
String s = demo.doSomething(1, "two", "third argument");
System.out.println(s);
模拟异常
List mockList = mock(List.class);
when(mockList.add(anyInt())).thenThrow(new RuntimeException("clear error"));
doThrow(new RuntimeException()).when(mockList).clear();

when 和 doThrow 的区别是什么?
when 用于定义方法调用时的返回值或行为。
doThrow 专门用于定义方法调用时抛出的异常

保证顺序

直接引用官网示例:

 // 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");
 // 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");
模拟链
@Mock
List mockList;

@Test
public void test() {

   when(mockList.add("some arg"))
           .thenThrow(new RuntimeException())
           .thenReturn(true);

   // 第一次调用抛出异常
   try {
       mockList.add("some arg");
   } catch (RuntimeException e) {

   }
   // 第二次调用返回true
   System.out.println(mockList.add("some arg"));
   // 连续调用返回true
   System.out.println(mockList.add("some arg"));
}

在不抛出异常的情况下我们可以进一步简化:

when(mockList.add("some arg")).
        thenReturn(true, false, true);
System.out.println(mockList.add("some arg"));
// 第二次调用返回true
System.out.println(mockList.add("some arg"));
// 连续调用返回true
System.out.println(mockList.add("some arg"));

结果为:true,false,true
我们需要注意和下面的区别:

when(mockList.add("some arg"))
        .thenReturn(true);
when(mockList.add("some arg"))
        .thenReturn(false);
System.out.println(mockList.add("some arg"));
System.out.println(mockList.add("some arg"));

这里第二个会覆盖第一个,结果为false。

真实模拟
List list = new LinkedList();
List spy = spy(list);
when(spy.size()).thenReturn(100);
spy.add("one");
spy.add("two");
System.out.println(spy.get(0));
System.out.println(spy.get(1));
System.out.println(spy.size());

结果:one,two,100
还记得前文我们提到mock是不会真实存储数据的,只是模拟行为,那么如果要存储真实数据用spy();
这里还有个易错点,你可能会像下面这样写,当get的时候返回一个数据,但是结果会抛出异常,如果你有需要获取还没有插入值的spy对象需要使用doReturn

List list = new LinkedList();
List spy = spy(list);
// 这里直接会抛出IndexOutOfBoundsException
when(spy.get(0)).thenReturn("foo");
// 如果想要解决上面的异常使用doReturn
doReturn("foo").when(spy).get(0);
System.out.println(spy.get(0));
捕获验证参数
public class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
public interface MockDemo {
    void doSomething(Person person);
}
@Test
public void testDoSomething() {
    // 创建模拟对象
    MockDemo mock = mock(MockDemo.class, Mockito.RETURNS_SMART_NULLS);
    // 创建 Person 对象
    Person john = new Person("John");
    // 调用 doSomething 方法
    mock.doSomething(john);
    // 使用 ArgumentCaptor 捕获参数
    ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);

    // 验证 doSomething 方法被调用,并捕获传入的 Person 参数
    verify(mock).doSomething(argument.capture());
    // 验证捕获到的 Person 对象的 name 属性是否为 "John"
    assertEquals("John", argument.getValue().getName());
}

上面这个例子是在调用doSomething方法的时候,捕获到这个参数,并且判断这个参数是否符合我们的预期。
官网有句话值得注意:
it is recommended to use ArgumentCaptor with verification but not with stubbing. Using ArgumentCaptor with stubbing may decrease test readability because captor is created outside of assert (aka verify or ‘then’) block. Also it may reduce defect localization because if stubbed method was not called then no argument is captured.

调用真实方法
when(mock.someMethod()).thenCallRealMethod();
重置
reset(mock);

之前的行为和打桩都会被重置。

注解
// 是下面代码的简写
// List list = new LinkedList();
// List spy = spy(list);
@Spy BeerDrinker drinker;

// 类似于@AutoWired 会进行注入
@InjectMocks LocalPub;

例子:

public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(int id) {
        return userRepository.findById(id);
    }
}
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testGetUserById() {
        // 创建模拟数据
        User user = new User(1, "John Doe");

        // 设置模拟行为
        when(userRepository.findById(1)).thenReturn(user);

        // 调用方法
        User result = userService.getUserById(1);

        // 验证结果
        assertEquals("John Doe", result.getName());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2147071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux命令:对文本文件的内容进行排序的工具sort详解

目录 一、概述 二、用法 1、 基本语法 2、 常用选项 3、获取帮助 三、示例 1. 基本用法 2. 按数字排序 3. 按第二列排序 4. 逆序排序 5. 删除重复行 6. 忽略大小写排序 7. 按人类可读的数值排序 8. 按版本号排序 四、高级用法 1. 与 uniq 结合使用去重 2. 与 gr…

1.使用 VSCode 过程中的英语积累 - File 菜单(每一次重点积累 5 个单词)

前言 学习可以不局限于传统的书籍和课堂&#xff0c;各种生活的元素也都可以做为我们的学习对象&#xff0c;本文将利用 VSCode 页面上的各种英文元素来做英语的积累&#xff0c;如此做有 3 大利 这些软件在我们工作中是时时刻刻接触的&#xff0c;借此做英语积累再合适不过&a…

不断挑战才有不断机遇!Eagle Trader等你来加入!

2024“Eagle Trader杯”全国职业交易联赛S1赛季已火热进行一个多月&#xff0c;吸引了超过355名交易员的积极参与&#xff01;目前&#xff0c;每天都有新的交易员踊跃报名参加&#xff01; 经过严格地交易考核&#xff0c;13名选手成功通过初试&#xff0c;正进入下一阶段的挑…

XILINX ZYNQ 7000 使用 FreeRTOS

XILINX 官方的SDK可以生成FreeRTOS 本文分为三个部分&#xff1a; 1.ZYNQ 7010 创建一个最小ZYNQ Processer系统&#xff0c;能够使用串口打印 2.使用SDK 创建一个FreeRTOS最小软件系统 3.浅析FreeRTOS最小软件系统 一&#xff1a;ZYNQ 7010 创建一个最小ZYNQ Processer系统&…

基于Linux系统离线安装oracle数据库

注意事项&#xff1a; 在安装的时候多次涉及root用户和oracle用户的切换&#xff0c;请注意区分&#xff0c;本文已明显 一、环境准备 1、关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld2、 禁用NetworkManager服务&#xff08;非必须&#xff09; [rootlocalhost …

信号与线性系统实验四 离散系统的时域及变换域分析

文章目录 一、实验目的二、实验内容与原理三、实验器材四、实验步骤五、实验数据及结果分析第一部分&#xff1a;离散时间信号的时域基本运算第二部分&#xff1a; 离散LTI系统的时域分析第三部分&#xff1a;离散LTI系统Z域分析 六、实验结论七、其他(主要是心得体会) 一、实验…

通信工程学习:什么是PON无源光网络

PON&#xff1a;无源光网络 PON&#xff08;Passive Optical Network&#xff0c;无源光纤网络&#xff09;是一种采用光分路器等无源光器件进行信号传输和分配的光纤接入技术。它利用光纤作为传输媒介&#xff0c;通过无源设备将光信号从中心局&#xff08;如光线路终端OLT&am…

Linux基础4-进程1(操作系统,进程介绍,Linux进程相关命令,getpid,fork)

上篇文章&#xff1a;Linux基础3-基础工具4&#xff08;git&#xff09;&#xff0c;冯诺依曼计算机体系结构-CSDN博客 本章重点&#xff1a; 1. 操作系统简介 2. 什么是进程&#xff1f; 3. 在Linux使用命令查看进程&#xff08;ps&#xff09; 4. getpid&#xff0c;getppid,…

卷积和相关

卷积和相关是两种运算关系(或过程),都是含参变量的无穷积分。都是两个函数通过某种运算得到另外一个函数。 卷积运算: 可用来表示一个观测系统或一个观 测仪器对输入信号的作用过程等。 相关运算:常用来比较两个函数的关联性,相似程度,用于信号检测,图像识别。如:在混…

SCSAI平台面向对象建模技术的设计和实现(1)

SCSAI平台面向对象建模技术的设计和实现&#xff08;1&#xff09; 原创 团长团 AI智造AI编程 2024年09月19日 20:09 北京 用爱编程30年&#xff0c;倾心打造工业和智能智造软件研发平台SCSAI,用创新的方案、大幅的让利和极致的营销&#xff0c;致力于为10000家的中小企业实现…

【jupyter notebook】环境部署及pycharm连接虚拟机和本地两种方式

Python数据处理分析简介 Python作为当下最为流行的编程语言之一 可以独立完成数据分析的各种任务数据分析领域里有海量开源库机器学习/深度学习领域最热门的编程语言在爬虫&#xff0c;Web开发等领域均有应用 与Excel&#xff0c;PowerBI&#xff0c;Tableau等软件比较 Excel有…

双立方(三次)卷积插值

前言 图像处理中有三种常用的插值算法&#xff1a; 最邻近插值 双线性插值 双立方&#xff08;三次卷积&#xff09;插值 其中效果最好的是双立方&#xff08;三次卷积&#xff09;插值&#xff0c;本文介绍它的原理以及使用 如果想先看效果和源码&#xff0c;可以拉到最底…

【论文阅读笔记】YOLOv10: Real-Time End-to-End Object Detection

论文地址&#xff1a;https://arxiv.org/abs/2405.14458 文章目录 论文小结论文简介论文方法为NMS-free训练的一致性双标签分配双标签分配一致性匹配度量 效率-精度整体驱动的模型设计效率驱动模型设计轻量级分类检测头Spatial-channel 解耦下采样Rank-guided block design 精度…

Vue3中的Pinia——管理应用程序的全局状态

介绍Pinia Pinia 是 Vue.js 的状态管理库&#xff0c;主要用于管理应用程序的全局状态。它是 Vuex 的替代品&#xff0c;提供了更简单和更灵活的 API。Pinia 的主要作用包括&#xff1a; 1. 状态管理&#xff1a;Pinia 允许你在应用中集中管理状态&#xff0c;方便不同组件之…

微服务以及注册中心

一、什么是微服务 微服务是指开发一个单个小型的但有业务功能的服务&#xff0c;每个服务都有自己的处理和轻量通讯机制&#xff0c;可以部署在单个或多个服务器上。微服务也指一种松耦合的、有一定的有界上下文的面向服务架构。也就是说&#xff0c;如果每个服务都要同时修改…

Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.

Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.Youmayincreasethelimitbyauthenticatingandupgrading:https://www.docker.com/increase−rate−limit.See ′ dockerrun−−help 在拉取docker进行的时候遇到这个问题,如何解决呢?本文提供的解决方…

石英晶体谐振器:核心功能材料及其工作原理与应用

晶发电子专注17年晶振生产,晶振产品包括石英晶体谐振器、振荡器、贴片晶振、32.768Khz时钟晶振、有源晶振、无源晶振等&#xff0c;产品性能稳定,品质过硬,价格好,交期快.国产晶振品牌您值得信赖的晶振供应商。 石英晶体谐振器&#xff0c;又称为无源晶振&#xff0c;是现代电子…

【代码】使用c#实现串口通信的基础模板

一、分享代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;using System.IO.Ports; using…

云平台在大规模设备管理和数据分析中的作用

在当代数字化转型的浪潮中&#xff0c;云平台作为信息技术基础设施的核心组件&#xff0c;扮演着无可替代的角色&#xff0c;尤其在大规模设备管理和数据分析领域&#xff0c;其重要性和影响力日益凸显。本文旨在深入探讨云平台如何通过其独特的优势&#xff0c;促进数据的高效…

ROS第五梯:ROS+VSCode+C++单步调试

解决问题&#xff1a;在ROS项目中进行断点调试。 第一步&#xff1a;创建一个ROS项目或者打开一个现有的ROS项目。 第二步&#xff1a;修改c_cpp_properties.json 增加一段命令: "compileCommands": "${workspaceFolder}/build/compile_commands.json"第三…