文章目录
本文参考:
【码农教程】手把手教你Mockito的使用 - 掘金 (juejin.cn)
java - doReturn().when()与when().thenReturn() - 成长之路 - SegmentFault 思否
单元测试实践篇:Mock_阿里巴巴淘系技术团队官网博客的博客-CSDN博客
阿里是如何进行单元测试培训的?_Hollis Chuang的博客-CSDN博客
【Mockito】Mockito + Junit 5 快速入门_哔哩哔哩_bilibili
1.为什么需要Mock
测试驱动的开发( TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。如下图所示:
为了测试类A,我们需要Mock B类和C类(用虚拟对象来代替)如下图所示:
2.Mockito 中常用方法
先添加maven依赖:
mockito和junit:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jxz</groupId><artifactId>MockitoLearning</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>4.3.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version></dependency></dependencies></project>
2.1 Mock 方法
mock 方法来自
org.mockito.Mock
,它表示可以 mock 一个对象或者是接口。
publicstatic<T>Tmock(Class<T> classToMock)
- classToMock:待 mock 对象的 class 类。
- 返回 mock 出来的类
实例:使用 mock 方法 mock 一个List类
List mockList =Mockito.mock(List.class);
也可以使用注解来快速模拟
- @Mock+MockitoAnnotations.openMocks(this)
也有用@Mock+MockitoAnnotations.initMocks(this)的
packagecom.jxz;importorg.junit.Before;importorg.junit.Test;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importjava.util.List;importstaticorg.mockito.Mockito.verify;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/publicclassMockExample1{@MockprivateList mockList;@Beforepublicvoidsetup(){MockitoAnnotations.openMocks(this);}@TestpublicvoidtestMock(){
mockList.add(1);verify(mockList).add(1);}}
- @Mock+@RunWith(MockitoJUnitRunner.class)
packagecom.jxz;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.Mock;importorg.mockito.junit.MockitoJUnitRunner;importjava.util.List;importstaticorg.mockito.Mockito.verify;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/@RunWith(MockitoJUnitRunner.class)publicclassMockExample2{@MockprivateList mockList;@TestpublicvoidtestMock(){
mockList.add(1);verify(mockList).add(1);}}
2.2 对 Mock 出来的对象进行行为验证和Junit结果断言
一旦mock对象被创建了,Mock对象会记录我们调用的所有交互,也就是各种方法和参数,验证的意思是”查看我们到底有没有调用过mock的这个方法“,Mockito 中验证的方法是:verify
packagecom.jxz;importorg.junit.Test;importorg.mockito.Mockito;importjava.util.List;importstaticorg.mockito.Mockito.verify;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/publicclassDemo{@TestpublicvoidtestMock(){List mockList =Mockito.mock(List.class);
mockList.add(1);
mockList.add("one");// verifyverify(mockList).add(1);// verify(mockList).add("two"); //会抛出异常}}
JUnit断言使用到的类是 Assert.
@TestpublicvoidtestMock2(){List mockList =Mockito.mock(List.class);
mockList.add(1);Assert.assertEquals(1,mockList.get(1));}
输出:
java.lang.AssertionError:
Expected :1
Actual :null
当使用 mock 对象时(这里是mockList),如果不对其行为进行定义(也就是下面的打桩),则 mock 对象方法的返回值为返回类型的默认值(这里为null)。
2.3 测试桩stub
指定mock对象的交互行为逻辑,基本格式
when().thenReturn()
when(mockedList.get(0)).thenReturn(“first”)规定当调用get(0)的时候返回"first"
@TestpublicvoidtestStub(){LinkedList mockList =Mockito.mock(LinkedList.class);// stubbingwhen(mockList.get(0)).thenReturn("first");when(mockList.get(1)).thenThrow(newRuntimeException());System.out.println(mockList.get(0));//firstSystem.out.println(mockList.get(1));//RuntimeException}
当我们连续两次为同一个方法使用stub的时候,他只会只用最新的一次。一旦这个方法被stub了,就会一直返回这个stub的值。如下:
@TestpublicvoidtestStub2(){LinkedList mockList =Mockito.mock(LinkedList.class);when(mockList.get(0)).thenReturn("first");when(mockList.get(0)).thenReturn("second");System.out.println(mockList.get(0));System.out.println(mockList.get(0));}
输出:
second
second
还有一种测试桩指定的方式,即
doReturn().when()
,本质上也是规定行为。
@TestpublicvoidtestDoReturn(){A a =newA();A mockA =Mockito.mock(A.class);Mockito.when(mockA.add(1,2)).thenReturn(5);// 当mockA调用add(1,2),返回5System.out.println(mockA.add(1,2));// 5A A2 =newA();A mockA2 =Mockito.mock(A.class);Mockito.doReturn(5).when(mockA2).add(1,2);// 同样是当mockA调用add(1,2),返回5System.out.println(mockA2.add(1,2));// 5}
两者本质上就是一个由于执行顺序产生的问题,
Mockito.when(mockA.add(1,2)).thenReturn(5)
,会先执行
a + b
,即
1 + 2
, 结果本应是3,但由于后面的
thenReturn
,所以调用该方法时,实际的返回值是5。而
Mockito.doReturn(5).when(mockA2).add(1,2)
就不会执行
a+b
的操作,而会直接返回5。区别就是若是一定会执行add()方法,难免会产生无法预估的副作用,比如抛出异常等。
2.4 参数匹配器
参数匹配器可以进行参数的灵活指派。
@TestpublicvoidtestMatch(){Map mockMap =Mockito.mock(Map.class);// 正常打桩测试when(mockMap.get("key1")).thenReturn("value1");System.out.println(mockMap.get("key1"));//value1// 任意String的参数匹配器when(mockMap.get(anyString())).thenReturn("value2");System.out.println(mockMap.get(anyString()));// value2System.out.println(mockMap.get("key2"));// value2System.out.println(mockMap.get(1));// null// 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常, put()返回参数when(mockMap.put(anyString(),anyInt())).thenReturn("value3");System.out.println(mockMap.put("key3",3));// value3System.out.println(mockMap.put(anyString(),anyInt()));// value3// System.out.println("key3",anyInt()); // 异常// verify也支持参数匹配verify(mockMap,atLeastOnce()).get(anyString());// 前面交互至少调用了一次get(anyString())verify(mockMap).put(anyString(),eq(3));// 前面至少调用了一次put(anyString(),3)}
2.5 mock()与spy()
- 被 spy 的对象会走真实的方法,而 mock 对象走虚拟对象的方法,返回默认值
- spy() 方法的参数是对象实例,mock()方法 的参数是 class
示例:
packagecom.jxz;importorg.junit.Test;importorg.mockito.Mockito;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/publicclassSpyAndMock{@TestpublicvoidtestSpy(){A a =newA();A a1 =Mockito.mock(A.class);A a2 =Mockito.spy(a);System.out.println(a1.add(1,2));System.out.println(a2.add(1,2));}}classA{publicintadd(int a,int b){return a+b;}}
输出:
03
通过mock生成的对象,会拥有以前的对象的所有方法,但是方法中都没有了功能,就比如上面的
a1
对应的类可以理解下面这样
A1 extend A{
pubic intadd(int a,int b){return0;}}
2.6 @InjectMocks
@InjectMocks和@Mock配合可以简化某个类中,注入类的配置。同时可以在外面对被@Mock的类进行行为指定,从而让其被调用时,产生我们指定的结果。
示例:
要测试的类和方法:
packagecom.jxz;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/publicclassRegistrationImpl{SalesDao salesDao =newSalesDao();SaveDao saveDao =newSaveDao();publicStringregister(int id,String name){String result1 = salesDao.findRep(name);System.out.println(result1);String result2 = saveDao.save(id, name);System.out.println(result2);return result1 +"_"+ result2;}}classSalesDao{publicStringfindRep(String name){return name;}}classSaveDao{publicStringsave(int id,String name){return id + name;}}
对应的测试类和方法:
packagecom.jxz;importorg.junit.Assert;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.junit.MockitoJUnitRunner;importstaticorg.mockito.Mockito.when;/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/@RunWith(MockitoJUnitRunner.class)publicclassRegistrationImplTest{@InjectMocksprivateRegistrationImpl registrationImpl;// 需要被注入mock对象的对象@MockprivateSalesDao salesDao;// RegistrationImpl中注入的类@MockprivateSaveDao saveDao;@TestpublicvoidtestRegister(){// 进行mock注入类的行为指定// 可以看到register方法中调用mock对象打印出来的东西正确when(salesDao.findRep("jiangxuzhao")).thenReturn("jiangxuzhao666");// jiangxuzhao666when(saveDao.save(123,"jiangxuzhao")).thenReturn("123jiangxuzhao666");// 123jiangxuzhao666String result = registrationImpl.register(123,"jiangxuzhao");Assert.assertEquals(result,"jiangxuzhao666_123jiangxuzhao666");// 结合mock对象的返回值正确// Assert.assertEquals(result,"null"); // org.junit.ComparisonFailure}}
版权归原作者 蒋大钊! 所有, 如有侵权,请联系我们删除。