0


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)publicclassInitMockOrSpyMethod1{@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@Testpublicvoidtest1(){// 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
*/publicclassInitMockOrSpyMethod2{privateUserService mockUserService;privateUserService spyUserService;@BeforeEachpublicvoidinit(){
        mockUserService =Mockito.mock(UserService.class);
        spyUserService =Mockito.spy(UserService.class);}@Testpublicvoidtest1(){// 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
*/publicclassInitMockOrSpyMethod3{@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@BeforeEachpublicvoidinit(){MockitoAnnotations.openMocks(this);}@Testpublicvoidtest1(){// 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)publicclassParamMatcherTest{@MockprivateUserService mockUserService;@Testpublicvoidtest4(){List<String> features =newArrayList<>();
        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
    */@Testpublicvoidtest3(){Mockito.doReturn(toBeReturned:99).when(mockUserService).modifyById(ArgumentMatchers.any(UserUpdateReq.class));UserUpdateReq userUpdateReq1 =newUserUpdateReq();
        userUpdateReq1.setId(1L);
        userUpdateReq1.setPhone("1L");Mockito.doReturn(toBeReturned:99).when(mockUserService).modifyById(userUpdateReq1);int result1 = mockUserService.modifyById(userUpdateReq1);UserUpdateReq userUpdateReq2 =newUserUpdateReq();
        userUpdateReq2.setId(2L);
        userUpdateReq2.setPhone("2L");Mockito.doReturn(toBeReturned:99).when(mockUserService).modifyById(userUpdateReq2);int result2 = mockUserService.modifyById(userUpdateReq2);}/**
    * 测试插桩时的参数匹配, 只拦截userUpdateReq1
    */@Testpublicvoidtest2(){UserUpdateReq userUpdateReq1 =newUserUpdateReq();
        userUpdateReq1.setId(1L);
        userUpdateReq1.setPhone("1L");//指定参数为userUpdateReq1时调用mockUserService.modifyById返回99Mockito.doReturn(toBeReturned:99).when(mockUserService).modifyById(userUpdateReq1);// 此处并不产生结果int result1 = mockUserService.modifyById(userUpdateReq1);// 此处产生结果UserUpdateReq userUpdateReq2 =newUserUpdateReq();
        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)
    */@Testpublicvoidtest1(){UserVO userVO = mockUserService.selectById(1);// nullSystem.out.println("userVO = "+ userVO);UserUpdateReq userUpdateReq1 =newUserUpdateReq();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)publicclassStubTest{@MockprivateList<String> mockList;@MockprivateUserServiceImp1 mockUserServiceImp1;@psyprivateUserServiceImp1 spyUserServiceImp1;/**
    * 测试verigy
    */@Testpublicvoidtest8(){
        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();}/**
    * 执行真正的原始方法
    */@Testpublicvoidtest7(){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 实现指定逻辑的插桩
    */@TestpublicvoidTest6(){when(mockList.get(anyInt())).thenAnswer(newAnswer<String>(){/**
            * 泛型表示要插桩方法的返回值类型
            */@OverridepublicStringanswer(InvocationOnMock invocation)throwsThrowable{// getArgument 表示获取插桩方法(此处就是List.get)的第几个参数值Integer argument = invocation.getArgument(0,Integer.class);returnString.vaLueOf(argument *100);}});String result = mockList.get(3);Assertions.assertEquals("300", result);}/**
    * 多次插桩
    */@Testpublicvoidtest5(){// 第一次调用返回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());}/**
    * 抛出异常
    */@Testpublicvoidtest4(){// 方法一doThrow(RuntimeException.class).when(mockList).clear();try{
            mockList.clear();// 走到下面这一行,说明插桩失败了Assertions.fail();}catch(Exception e){// 断言表达式为真Assertions.assertTrue(e instanceofRuntimeException);}// 方法二when(mockList.get(anyInt)).thenThrow(RuntimeException.class);try{
            mockList.get(4);Assertions.fail();}catch(Exception e){Assertions.assertTrue(e instanceofRuntimeException);}}/**
    * 插桩的两种方式
    */@Testpublicvoidtest3(){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 返回值插桩
    */@Testpublicvoidtest2(){// 调用mockList.clear的时候什么也不做doNothing().when(mockList).clear();
        mockList.clear();// 验证调用了一次clearverfy(mockList,times(wantedNumberOfInvocations:1)).clear();}/**
    * 指定返回值
    */@TestpublicvoidTest1{// 方法一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)publicclassInjectMocksTest{/**
    * 1.被@InjectMocks标注的属性必须是实现类,因为mockito会创建对应的示例对象,默认创建的对象就是未经过mockito处理的普通对象,因此常配合@Spy注解使其变为默认调用真实方法的mock对象
    * 2.mockito会使用spy或mock对象注入到@InjectMocks对应的示例对象中
    */@Spy@InjectMocksprivateUserService userService;@MockprivateUserFeatureService userFeatureService;@MockprivateList<String> mockList;@Testpublicvoidtest1(){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)publicclassAssertTest{@MockprivateList<String> mockList;@Testpublicvoidtest1(){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)publicclassUserServiceImplInSpringTest{/**
    * 不能配置@Spy: Argument passed to when() is not mock!
    */@SpyBean@ResourceprivateUserServiceImp1 userService;@MockprivateUserFeatureService userFeatureService;@MockprivateUserMapper userMapper;@TestpublicvoidtestSelectById3(){//配置方法getById的返回值UserDo ret =newUserDo();
        ret.setId(1L);
        ret.setUsername("乐之者java");
        ret.setPhone("http://www.roadjava.com");doReturn(ret).when(userService).getById(1L);//配置userFeatureService.seLectByUserId的返回值List<UserFeatureDO> userFeatureDoList =newArrayList<>();UserFeatureDO userFeatureDO =newUserFeatureDo();
        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());}@TestpublicvoidtestSelectById2(){// 配置方法getById的返回值UserDo ret =newUserDo();
        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);}@TestpublicvoidtestSelectById1(){// 配置doReturn(userMapper).when(userService).getBaseMapper();UserVO userVO = userService.selectById(1L);Assertions.assertNull(userVO);}}

漫谈

image-20240921152749306


本文转载自: https://blog.csdn.net/2301_79083000/article/details/142425264
版权归原作者 陈逸轩*^_^* 所有, 如有侵权,请联系我们删除。

“mockito+junit搞定单元测试(2h)”的评论:

还没有评论