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

ops/2024/9/23 6:23:22/

一,简介

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

java">/**
* 初始化 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 参数匹配

java">/**
* 参数匹配; 通过方法签名(参数)来指定哪些方法调用需要被处理(插桩,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的使用

java">/**
* @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注入、字段反射注入
java">@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 原生断言

java">@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被测试类的某些方法时可以使用
java">/**
* 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.ppmy.cn/ops/114623.html

相关文章

STM32 单片机最小系统全解析

STM32 单片机最小系统全解析 本文详细介绍了 STM32 单片机最小系统&#xff0c;包括其各个组成部分及设计要点与注意事项。STM32 最小系统在嵌入式开发中至关重要&#xff0c;由电源、时钟、复位、调试接口和启动电路等组成。 在电源电路方面&#xff0c;采用 3.3V 直流电源供…

【数据结构与算法 | 灵神题单 | 栈基础篇】力扣155, 1472, 1381

1. 力扣155&#xff1a;最小栈 1.1 题目&#xff1a; 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆…

修复 blender 中文输入 BUG (linux/wayland/GNOME/ibus)

blender 是一个很好的 开源 3D 建模/动画/渲染 软件, 功能很强大, 跨平台 (GNU/Linux, Windows 等系统都支持). 然而, 窝突然发现, blender 居然不支持中文输入 (linux) ! 这怎么能忍 ? 再一查, 不得了, 这居然是个 3 年前一直未解决的陈年老 BUG. 不行, 这绝对忍不了, 这个 …

招联金融秋招-2025

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策划 产品运营…

SpringBoot中对数据库连接配置信息进行加密处理

1 在项目中加密 1.1 导包 <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.5</version> </dependency>1.2 加密 配置文件 jasypt:encryptor:p…

网络安全-LD_PRELOAD,请求劫持

目录 一、环境 二、开始做题 三、总结原理 四、如何防护 一、环境 我们这里用蚁剑自带的靶场第一关来解释 docker制作一下即可 二、开始做题 首先环境内很明显给我们已经写好了webshell 同样我们也可以访问到 我们使用这个蚁剑把这个webshell连上 我们发现命令不能执行&am…

python基础题练习

1.可否定义一个sum函数呢&#xff1f;返回指定区间的值的和&#xff1f;例如&#xff0c;区间[1,4]的和为123410返回指定区间值的平方的和呢&#xff1f;立方呢&#xff1f; 代码&#xff1a; # 计算从start到end&#xff08;包括end&#xff09;的所有整数的和。 def sum_ra…

【机器学习】——线性回归(自我监督学习)

文章目录 1. 线性回归的定义2. 线性回归的模型3. 线性回归的核心思想4. 线性回归的求解5. 线性回归的假设6. 模型评估7. 线性回归的优缺点8. 线性回归的扩展9. 线性回归的实际应用10. 示例代码&#xff08;Python实现&#xff09; 线性回归详细介绍 1. 线性回归的定义 线性回归…