mockito+junit搞定单元测试(2h)

一,简介

1.1 单元测试的特点

  • 配合断言使用(杜绝 System.out )
  • 可重复执行
  • 不依赖环境
  • 不会对数据产生影响
  • spring 的上下文环境不是必须的
  • 一般都需要配合 mock 类框架来实现

1.2 mock 类框架使用场景

要进行测试的方法存在外部依赖(如 db, redis, 第三方接口调用等), 为了能够专注于对该方法(单元)的逻辑进行测试,就希望能虚拟出外部依赖,避免外部依赖成为测试的阻塞项,一般都是测试 service 层即可。

1.3 常用 mock类框架

mock 类框架;用于 mock 外部依赖

1.3.1 mockito

名称:ito: input to output

官网: https://site.mockito.org

官网文档: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

限制:老版本对于 fianl class, final method, static method, private method 均不能被 mockito mock, 目前已支持final class, final method, static method 的 mock, 具体可以参考官网

原理:bytebuddy, 教程: https://www.bilibili.com/video/BV1G24y1a7bd

1.3.2 easymock
1.3.3 powermock

官网:https://github.com/powermock/powermock

与mockito的版本支持关系:https://gitee.com/mirrors/powermock/wikis/Mockito#supported-versions 对 mockito 或 easymock 的增强

1.3.4 JMockit

二,mockito 的单独使用

2.1 mock 对象与 spy 对象

方法插桩方法不插桩作用对象最佳实践
mock 对象执行插桩逻辑返回mock对象的默认值类,接口被测试类或其他依赖
spy 对象执行插桩逻辑调用真实方法类,接口被测试类

2.2 初始化 mock/spy 对象的方式

方法一方法二方法三
junit4@RunWith(MockitoJUnitRunner.class) + @Mock等注解Mockito.mock(X.class)等静态方法MockitoAnnotations.openMocks(this)+@Mock等注解
junit5@ExtendWith(MockitoExtension.class) + @Mock等注解
示例

image-20240921185259112

/**
* 初始化 mock/spy 对象的方式有三种,第一种
* @author zhangdaowen
*/
@ExtendWith(MockitoExtension.class)
public class InitMockOrSpyMethod1 {@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@Testpublic void test1() {// true 判断某对象是不是mock对象System.out.println(" " + Mockito.mockingDetails(mockUserService).isMock().isMOck());// falseSystem.out.println(""+Mockito.mockingDetails(mockUserService).isMock().isSpy());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());}
}
/**
* 初始化 mock/spy 对象的方式有三种,第二种
* @author zhangdaowen
*/
public class InitMockOrSpyMethod2 {private UserService mockUserService;private UserService spyUserService;@BeforeEachpublic void init() {mockUserService = Mockito.mock(UserService.class);spyUserService = Mockito.spy(UserService.class);}@Testpublic void test1() {// true 判断某对象是不是mock对象System.out.println(" " + Mockito.mockingDetails(mockUserService).isMock().isMOck());// falseSystem.out.println(""+Mockito.mockingDetails(mockUserService).isMock().isSpy());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());}
}
/**
* 初始化 mock/spy 对象的方式有三种,第三种
* @author zhangdaowen
*/
public class InitMockOrSpyMethod3 {@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@BeforeEachpublic void init() {MockitoAnnotations.openMocks(this);}@Testpublic void test1() {// true 判断某对象是不是mock对象System.out.println(" " + Mockito.mockingDetails(mockUserService).isMock().isMOck());// falseSystem.out.println(""+Mockito.mockingDetails(mockUserService).isMock().isSpy());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());System.out.println(""+Mockito.mockingDetails(spyUserService).isMock());}
}

2.3 参数匹配

/**
* 参数匹配; 通过方法签名(参数)来指定哪些方法调用需要被处理(插桩,verify验证)
* @author zhangdaowen
*/
@ExtendWith(MockitoExtension.class)
public class ParamMatcherTest {@Mockprivate UserService mockUserService;@Testpublic void test4(){ List<String> features = new ArrayList<>();mockUserService.add("乐之者Java", phone:"123", features);// 校验参数为 "乐之者Java", "123", features 的 add 方法调用了1次Mockito.verify(mockUserService,MOckito.times(wantedNumberOfInvocations:1)).add("乐之者Java", phone:"123", features);// 报错:When using matchers, aLL arguments have to be provided by matches//Mockito.verify(mockUserService,Mockito.times(wantedNumberOfInvocations:1)).add(ArgumentMatchers.anyString(), "123", features);Mockito.verify(mockUserService,Mockito.times(wantedNumberOfInvocations:1)).add(ArgumentMatchers.anyString(), anyString, anyList());}/***  ArgumentMatchers.any 拦截 UserUpdateReq 类型的任意对象* 除了any, 还有anyXX(anyLong, anyString) 注意:它们都不包括null*/@Testpublic void test3() {Mockito.doReturn(toBeReturned:99).when(mockUserService).modifyById(ArgumentMatchers.any(UserUpdateReq.class));UserUpdateReq userUpdateReq1 = new UserUpdateReq();userUpdateReq1.setId(1L);userUpdateReq1.setPhone("1L");Mockito.doReturn(toBeReturned: 99).when(mockUserService).modifyById(userUpdateReq1); int result1 = mockUserService.modifyById(userUpdateReq1); UserUpdateReq userUpdateReq2 = new UserUpdateReq();userUpdateReq2.setId(2L);userUpdateReq2.setPhone("2L");Mockito.doReturn(toBeReturned: 99).when(mockUserService).modifyById(userUpdateReq2); int result2 = mockUserService.modifyById(userUpdateReq2);}/*** 测试插桩时的参数匹配, 只拦截userUpdateReq1*/@Testpublic void test2() {UserUpdateReq userUpdateReq1 = new UserUpdateReq();userUpdateReq1.setId(1L);userUpdateReq1.setPhone("1L");//指定参数为userUpdateReq1时调用mockUserService.modifyById返回99Mockito.doReturn(toBeReturned: 99).when(mockUserService).modifyById(userUpdateReq1); // 此处并不产生结果int result1 = mockUserService.modifyById(userUpdateReq1); // 此处产生结果UserUpdateReq userUpdateReq2 = new UserUpdateReq();userUpdateReq2.setId(2L);userUpdateReq2.setPhone("2L");//指定参数为userUpdateReq1时调用mockUserService.modifyById返回99Mockito.doReturn(toBeReturned: 99).when(mockUserService).modifyById(userUpdateReq2); int result2 = mockUserService.modifyById(userUpdateReq2); }/*** 对于 mock 对象不会调用真实方法,直接返回 mock对象的默认值* 默认值(int), null(UserVO), 空集合(List)*/@Testpublic void test1() {UserVO userVO = mockUserService.selectById(1);// nullSystem.out.println("userVO = " + userVO);UserUpdateReq userUpdateReq1 = new UserUpdateReq();int i = mockUserService.modifyById(UserUpdateReq1);System.out.println("i=" + i);}
}

2.4 方法插桩

指定调用某个方法时的行为(stubbing),达到相互隔离的目的

  • 返回指定值

  • void返回值方法插桩

  • 插桩的两种方式

    • when(obj.someMethod()).thenXxx():其中 obj 可以是 mock 对象
    • doXxx(.when(obj).someMethod(): 其中 obj 可以是 mock/spy 对象 或者是无返回值的方法进行插桩
  • 抛异常

  • 多次插桩

  • thenAnswer

  • 执行真正的原始方法

  • verify的使用

/**
* @author zhaodaowen
* @see <a href="http://www.roadjava.com">乐之者java</a>
*/
@ExtendWith(MockitoExtension.class)
public class StubTest {@Mockprivate List<String> mockList;@Mockprivate UserServiceImp1 mockUserServiceImp1;@psyprivate UserServiceImp1 spyUserServiceImp1;/*** 测试verigy*/@Testpublic void test8() {mockList.add("one");// true: 调用mock对象的写操作方法是没效果的Assertions.assertEquals(0, mockList.size());mockList.clear();// 验证调用过1次add方法,且参数必须是oneverify(mockList)// 指定要验证的方法和参数,这里不是调用,也不会产生作用效果.add("oen");// 等价于上面的verigy(mockedList)verify(mockList, times(1)).clear();// 检验没有调用的两种方式verify(mockList, times(0)).clear();verify(mockList, never()).get(1);// 检验最少或最多调用了多少次verify(List, atLeast(1)).clear();verify(List, atMost(3)).clear();}/*** 执行真正的原始方法*/@Testpublic void test7() {when(mockUserServiceImpl.getNUmber()).thenCallRealMethod();int number = mockUserServiceImp1.getNumber();Assertions.assertEquals(0, number);// spy对象默认就会调用真实方法,如果不想让它调用,需要单独为它进行插桩int spyResult = spyUserServiceImpl.getNumber();Assertions.assertEquals(0, spyResult);// 不让spy对象调用真实方法doReturn(1000).when(spyUserServiceImpl).getNumber();spyResult = spyUserServiceImpl.getNumber();Assertions.assertEquals(1000, spyResult);}/*** thenAnswer 实现指定逻辑的插桩*/@Testpublic void Test6() {when(mockList.get(anyInt())).thenAnswer(new Answer<String>() {/*** 泛型表示要插桩方法的返回值类型*/@Overridepublic String answer(InvocationOnMock invocation) throws Throwable {// getArgument 表示获取插桩方法(此处就是List.get)的第几个参数值Integer argument = invocation.getArgument(0, Integer.class);return String.vaLueOf(argument * 100);}});String result = mockList.get(3);Assertions.assertEquals("300", result);}/*** 多次插桩*/@Testpublic void test5() {// 第一次调用返回1, 第二次调用返回2, 第3次及之后的调用都返回3//     when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3);// 可间接写为when(mockList.size()).thenReturn(1, 2, 3);Assertions.assertEquals(1, mockList.size());Assertions.assertEquals(2, mockList.size());Assertions.assertEquals(3, mockList.size());}/*** 抛出异常*/@Testpublic void test4() {// 方法一doThrow(RuntimeException.class).when(mockList).clear();try {mockList.clear();// 走到下面这一行,说明插桩失败了Assertions.fail();} catch (Exception e) {// 断言表达式为真Assertions.assertTrue(e instanceof RuntimeException);}// 方法二when(mockList.get(anyInt)).thenThrow(RuntimeException.class);try {mockList.get(4);Assertions.fail();} catch (Exception e) {Assertions.assertTrue(e instanceof RuntimeException);}}/*** 插桩的两种方式*/@Testpublic void test3() {when(mockUserServiceImp1.getNumber()).thenReturn(99);// mockUserServiceImpl.getNumber() = 99 不调用真实方法System.out.println("" + mockUserServiceImp1.getNumber());when(spyUserServiceImp1.getNumber()).thenReturn(99);// getNumber// spyUserServiceImpl.getNumber() = 99// spy对象在没有插桩时是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的// 需使用doXxx.when(obj).someMethod();其中obj可以是mock/spy对象System.out.println("" + spyUserServiceImp1.getNumber());doReturn(1000).when(spyUserServiceImpl).getNumber();}/*** void 返回值插桩*/@Testpublic void test2() {// 调用mockList.clear的时候什么也不做doNothing().when(mockList).clear();mockList.clear();// 验证调用了一次clearverfy(mockList,times(wantedNumberOfInvocations:1)).clear();}/*** 指定返回值*/@Testpublic void Test1 {// 方法一doReturn("zero").when(mockList).get(0);// 如果返回值不相等则本单元测试会失败Assertions.assertEquals("zero", mockList.get(0));//方法二when(mockList.get(1)).thenReturn("one");Assertions.assertEquals("one", mockList.get(1));}
}

2.5 @InjectMocks注解的使用

  • 作用:若@InjectMocks 声明的变量需要用到 mock/spy 对象,mockito 会自动使用当前类里的 mock 或 spy 成员进行按类型或名字的注入
  • 原理:构造器注入、setter注入、字段反射注入
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {/*** 1.被@InjectMocks标注的属性必须是实现类,因为mockito会创建对应的示例对象,默认创建的对象就是未经过mockito处理的普通对象,因此常配合@Spy注解使其变为默认调用真实方法的mock对象* 2.mockito会使用spy或mock对象注入到@InjectMocks对应的示例对象中*/@Spy@InjectMocksprivate UserService userService;@Mockprivate UserFeatureService userFeatureService;@Mockprivate List<String> mockList;@Testpublic void test1() {int number = userService.getNumber();Assertions.assertEquals(0, number);}
}

2.6 断言工具

namcrest:junit4 中引入的第三方断言库,junit5 中被移出,从 1.3 版本后,坐标由 org.hamcrest:hamcrest 变为org.hamcrest:hamcrest

assert: 常用的断言库

junit4 原生断言

junit5 原生断言

@ExtendWith(MockitoExtension.class)
public class AssertTest {@Mockprivate List<String> mockList;@Testpublic void test1() {when(mockList.size()).thenReturn(999);// 测试hamcrest的断言MatchAssert.assertThat(mockList.size(), IsEqual.equalTo(999));// 测试 assertJ assertThat: 参数为实际的值Assertions.assertThat(mockList.size().isEquaTo(999));// junit5原生断言orj.junit.jupiter.api.Assertions.assertEquals(999, mockList.size());// junit4原生断言org.junit.Assert.assertEquals(999, mockList.size());}}

三,实战讲解

四,mockito在springboot环境使用(不推荐-)

生成的对象受 spring 管理,相当于自动替换对应类型 bean 的注入

@MockBean

  • 类似@Mock
  • 用于通过类型或名字 替换 spring 容器中已经存在的bean,从而达到对这些bean进行mock的目的

@SpyBean

  • 作用类似@Spy
  • 用于通过类型或名字包装spring容器中已经存在的bean,当需要mock被测试类的某些方法时可以使用
/**
* Mock配合spring使用
**/
@SpringBootTest(class = MockitoApp.class)
public class UserServiceImplInSpringTest {/*** 不能配置@Spy: Argument passed to when() is not mock!*/@SpyBean@Resourceprivate UserServiceImp1 userService;@Mockprivate UserFeatureService userFeatureService;@Mockprivate UserMapper userMapper;@Testpublic void testSelectById3() {//配置方法getById的返回值UserDo ret = new UserDo();ret.setId(1L);ret.setUsername("乐之者java");ret.setPhone("http://www.roadjava.com");doReturn(ret).when(userService).getById(1L);//配置userFeatureService.seLectByUserId的返回值List<UserFeatureDO> userFeatureDoList = new ArrayList<>();UserFeatureDO userFeatureDO = new UserFeatureDo();userFeatureD0.setId(88L);userFeatureD0.setUserId(1L);userFeatureDO.setFeatureValue("aaaa");userFeatureDoList,add(userFeatureDo);doReturn(userFeatureDoList).when(userFeatureService).selectByUserId(lL);// 执行测试UserVo userVO = userService,selectById(userld:1L);// 断言Assertions.assertEquals(expected:1,userVO.getFeatureValue().size());}@Testpublic void testSelectById2() {// 配置方法getById的返回值UserDo ret = new UserDo();ret.setId(1L);ret.setUsername("乐之者java");ret.setPhone("http://www.rodajava.com"); // up主的广告doReturn(ret).when(userService).getById(1L);UserVo userVO = userService.selectById(1L);Assertions.assertNotNUll(userVO);}@Testpublic void testSelectById1() {// 配置doReturn(userMapper).when(userService).getBaseMapper();UserVO userVO = userService.selectById(1L);Assertions.assertNull(userVO);}
}

漫谈

image-20240921152749306

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

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

相关文章

在Linux中运行flask项目

准备 这里我准备了一个GitHub上某个大佬写的留言板的Flask项目&#xff0c;就用这个来给大家做示范了。 查看留言板的目录结构 查看主程序所用的库函数 只有一个第三方库 Flask 安装pip sudo apt install python3-pip -y测试 pip 安装成功 修改pip镜像源 修改pip的默认下载…

Django学习实战之评论验证码功能(附A)

前言&#xff1a; 对于具有评论功能的博客来说&#xff0c;无论是否为知名博客&#xff0c;都会被恶意广告关注&#xff0c;上线没几天就会有人开始通过程序灌入广告评论&#xff0c;因此针对所有有用户输入的页面&#xff0c;验证码是必需品。 在Django系统中使用验证码非常简…

[Python数据可视化]探讨数据可视化的实际应用:三个案例分析

数据可视化是理解复杂数据集的重要工具&#xff0c;通过图形化的方法&#xff0c;可以直观地展示信息、趋势和模式。本文将深入探讨三个实际案例&#xff0c;包括健康数据分析、销售趋势分析、城市交通流量分析。每个案例将提供假设数据、详细注释的代码及分析结果。 案例 1: …

【每日刷题】Day128

【每日刷题】Day128 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 194. 二叉树的最近公…

Spring在不同类型之间也能相互拷贝?

场景还原 日常开发中&#xff0c;我们会定义非常多的实体&#xff0c;例如VO、DTO等&#xff0c;在涉及实体类的相互转换时&#xff0c;常使用Spring提供的BeanUtils.copyProperties&#xff0c;该类虽好&#xff0c;可不能贪用。 这不在使用过程中就遇到一个大坑&#xff0c…

逻辑分析仪看波形方法

一、串口波形讲解 异步串行数据的一般格式是&#xff1a;起始位数据位停止位&#xff0c;其中起始位1 位&#xff0c;数据位可以是5、6、7、8位&#xff0c;停止位可以是1、1.5、2位。 对于正逻辑的TTL电平&#xff0c; a.起始位是一个值为0的位&#xff0c;低电平&#xff…

leetcode练习 二叉树的最大深度

给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3提示&#xff1a; 树中节点的数量在 [0, 104] 区间内。-100 …

【图像检索】基于Gabor特征的图像检索,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于Gabor特征的图像检索&#xff0c;用matlab实现。 一、案例背景和算法介绍 这次博…

排序----快速排序(快排)(递归版)

首先讲一下单趟的思路&#xff1a; 在这一块数据中&#xff0c;记录第一个元素为key&#xff0c;然后设置L和R两个指针&#xff0c;L找比key处的元素大的&#xff0c;R找比key处元素小的&#xff0c;找到了就交换这两个位置的元素。当两个指针相遇时&#xff0c;若相遇点的元素…

20240921在友善之臂的NanoPC-T6开发板上确认宸芯的数传模块CX6602N的AT命令

console:/dev # cat ttyUSB1 & console:/dev # echo AT > ttyUSB1 20240921在友善之臂的NanoPC-T6开发板上确认宸芯的数传模块CX6602N的AT命令 2024/9/21 21:03 【必须】Android12/Linux&#xff08;Buildroot&#xff09;都必须要&#xff01; 4、【Android12默认打开U…

https的连接过程

根证书: 内置在操作系统和浏览器中,可手动添加,下级是中间证书或服务器证书,只有当中间证书或服务器证书关联到已存在的根证书时,中间证书或服务器证书才视为有效 中间证书: 位于根证书和服务器证书之间,他们之间也可以没有中间证书,作用是对根证书增加一个下级,方便管理,由根…

GAMES101(作业4~5)

作业四 题目&#xff1a; 由 4 个控制点表示的 Bzier 曲线&#xff0c; bezier&#xff1a;该函数实现绘制 Bzier 曲线的功能。它使用一个控制点序列和一个 OpenCV&#xff1a;&#xff1a;Mat 对象作为输入&#xff0c;没有返回值。它会使 t 在 0 到 1 的范围内进 行迭代&a…

【Linux】进程地址空间和进程调度队列

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12625432.html 目录 问题现象 进程地址空间 进一步理解 地址空间 Linux2.6内核进程调度队列 …

RecyclerView的notifyDataSetChanged和notifyItemRemoved之间的区别

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 RecyclerView 提供了多种方法来通知适配器&#xff08;Adapter&#xff09;数据集发生变化&#xff0c;其中 notifyDataSetChanged() 和 notify…

数据库系统基础概述

文章目录 前言一、数据库基础概念 1.数据库系统的组成2.数据模型3.数据库的体系结构二、MySQL数据库 1.了解MySQL2.MySQL的特性3.MySQL的应用场景总结 前言 MySQL数据库是一款完全免费的产品&#xff0c;用户可以直接从网上下载使用&#xff0c;不用花费任何费用。这点对于初学…

proteus仿真学习(1)

一&#xff0c;创建工程 一般选择默认模式&#xff0c;不配置pcb文件 可以选用芯片型号也可以不选 不选则从零开始布局&#xff0c;没有初始最小系统。选用则有初始最小系统以及基础的main函数 本次学习使用从零开始&#xff0c;不配置固件 二&#xff0c;上手软件 1.在元件…

【AcWing】875. 快速幂

#include<iostream> using namespace std; typedef long long LL;LL qmi(int a,int b,int p){LL res1%p;//%p是为了p1的时候&#xff0c;余数是0while(b){if(b&1) resres*a%p;//位数是1的b>>1;aa*(LL)a%p;//a*a再modp是为了防止溢出}return res; }int main(){i…

【动态规划】(三)动态规划——完全背包

动态规划——完全背包 完全背包理论基础零钱兑换Ⅱ组合总和Ⅳ爬楼梯&#xff08;进阶版&#xff09;零钱兑换完全平方数单词拆分背包问题总结 完全背包理论基础 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品…

零食店小程序发展客户转化运营

零食店、折扣店近些年市场中跑出了不少区域性、多地化的品牌&#xff0c;直营及加盟模式&#xff0c;还有各种超市、商场、街边小店等&#xff0c;零食基本没有年龄群体限制&#xff0c;又属于常消费品&#xff0c;线上线下生意都可以进行发展。 线下客户到店&#xff0c;线上…

链表数据结构

链表可以解决顺序表的缺点 我们今天简单引用下链表 这边是代码讲解 头文件 #pragma once #include<stdio.h> #include<iostream> #include<string.h> #include<stdlib.h> using namespace std; typedef struct student {union {int data;int len;};s…