Java单元测试框架之Mockito指南 您所在的位置:网站首页 java单元测试mock没有返回 Java单元测试框架之Mockito指南

Java单元测试框架之Mockito指南

2024-07-09 06:34| 来源: 网络整理| 查看: 265

关注开源优测不迷路

大数据测试过程、策略及挑战

测试框架原理,构建成功的基石

在自动化测试工作之前,你应该知道的10条建议

在自动化测试中,重要的不是工具

概述

Mockito 框架是用于单元测试的基本框架,本文将介绍其使用使用方法及作用,也会给出相对应的例子作为参考。详细的业务场景可以参考一下项目中的单元测试编写。文中最后也有关于单元测试的相关文章链接,大家可以去详细了解一下。

一、 行为验证

一旦 mock 对象被创建了,mock 对象会记住所有的交互,然后你就可以选择性的验证你感兴趣的交互,验证不通过则抛出异常。

@Test public void test1() { final List mockList = Mockito.mock(List.class); mockList.add("mock1"); mockList.get(0); mockList.size(); mockList.clear(); // 验证方法被使用(默认1次) Mockito.verify(mockList).add("mock1"); // 验证方法被使用1次 Mockito.verify(mockList, Mockito.times(1)).get(0); // 验证方法至少被使用1次 Mockito.verify(mockList, Mockito.atLeast(1)).size(); // 验证方法没有被使用 Mockito.verify(mockList, Mockito.never()).contains("mock2"); // 验证方法至多被使用5次 Mockito.verify(mockList, Mockito.atMost(5)).clear(); // 指定方法调用超时时间 Mockito.verify(mockList, timeout(100)).get(0); // 指定时间内需要完成的次数 Mockito.verify(mockList, timeout(200).atLeastOnce()).size(); } 二、如何做一些测试桩 stub

默认情况下,所有的函数都有返回值。mock 函数默认返回的是 null,一个空的集合或者一个被对象类型包装的内置类型,例如 0、false 对应的对象类型为 Integer、Boolean;

一旦测试桩函数被调用,该函数将会一致返回固定的值;

对于 static 和 final 方法, Mockito 无法对其 when (…).thenReturn (…) 操作。

@Test public void test2() { //静态导入,减少代码量:import static org.mockito.Mockito.*; final ArrayList mockList = mock(ArrayList.class); // 设置方法调用返回值 when(mockList.add("test2")).thenReturn(true); doReturn(true).when(mockList).add("test2"); System.out.println(mockList.add("test2")); //true // 设置方法调用抛出异常 when(mockList.get(0)).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(mockList).get(0); System.out.println(mockList.get(0)); //throw RuntimeException // 无返回方法打桩 doNothing().when(mockList).clear(); // 为回调做测试桩(对方法返回进行拦截处理) final Answer answer = new Answer() { @Override public String answer(InvocationOnMock invocationOnMock) throws Throwable { final List mock = (List) invocationOnMock.getMock(); return "mock.size result => " + mock.size(); } }; when(mockList.get(1)).thenAnswer(answer); doAnswer(answer).when(mockList).get(1); System.out.println(mockList.get(1)); //mock.size result => 0 // 对同一方法多次打桩,以最后一次为准 when(mockList.get(2)).thenReturn("test2_1"); when(mockList.get(2)).thenReturn("test2_2"); System.out.println(mockList.get(2)); //test2_2 System.out.println(mockList.get(2)); //test2_2 // 设置多次调用同类型结果 when(mockList.get(3)).thenReturn("test2_1", "test2_2"); when(mockList.get(3)).thenReturn("test2_1").thenReturn("test2_2"); System.out.println(mockList.get(3)); //test2_1 System.out.println(mockList.get(3)); //test2_2 // 为连续调用做测试桩(为同一个函数调用的不同的返回值或异常做测试桩) when(mockList.get(4)).thenReturn("test2").thenThrow(new RuntimeException()); doReturn("test2").doThrow(new RuntimeException()).when(mockList).get(4); System.out.println(mockList.get(4)); //test2 System.out.println(mockList.get(4)); //throw RuntimeException // 无打桩方法,返回默认值 System.out.println(mockList.get(99)); //null } 三、参数匹配器

参数匹配器使验证和测试桩变得更灵活;

为了合理的使用复杂的参数匹配,使用 equals () 与 anyX () 的匹配器会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用 equals () 匹配或者实现 equals () 函数来帮助你进行测试;

如果你使用参数匹配器,所有参数都必须由匹配器提供;

支持自定义参数匹配器;

@Test public void test3() { final Map mockMap = mock(Map.class); // 正常打桩测试 when(mockMap.get("key")).thenReturn("value1"); System.out.println(mockMap.get("key")); //value1 // 为灵活起见,可使用参数匹配器 when(mockMap.get(anyString())).thenReturn("value2"); System.out.println(mockMap.get(anyString())); //value2 System.out.println(mockMap.get("test_key")); //value2 System.out.println(mockMap.get(0)); //null // 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常 when(mockMap.put(anyString(), anyInt())).thenReturn("value3"); System.out.println(mockMap.put("key3", 3)); //value3 System.out.println(mockMap.put(anyString(), anyInt())); //value3 System.out.println(mockMap.put("key3", anyInt())); //异常 // 行为验证时,也支持使用参数匹配器 verify(mockMap, atLeastOnce()).get(anyString()); verify(mockMap).put(anyString(), eq(3)); // 自定义参数匹配器 final ArgumentMatcher myArgumentMatcher = new ArgumentMatcher() { @Override public boolean matches(ArgumentTestRequest request) { return "name".equals(request.getName()) || "value".equals(request.getValue()); } }; // 自定义参数匹配器使用 final ArgumentTestService mock = mock(ArgumentTestService.class); when(mock.argumentTestMethod(argThat(myArgumentMatcher))).thenReturn("success"); doReturn("success").when(mock).argumentTestMethod(argThat(myArgumentMatcher)); System.out.println(mock.argumentTestMethod(new ArgumentTestRequest("name", "value"))); // success System.out.println(mock.argumentTestMethod(new ArgumentTestRequest())); //null } 四、执行顺序验证

验证执行顺序是非常灵活的 - 你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可;

你可以仅通过那些需要验证顺序的 mock 对象来创建 InOrder 对象;

@Test public void test4() { // 验证同一个对象多个方法的执行顺序 final List mockList = mock(List.class); mockList.add("first"); mockList.add("second"); final InOrder inOrder = inOrder(mockList); inOrder.verify(mockList).add("first"); inOrder.verify(mockList).add("second"); // 验证多个对象多个方法的执行顺序 final List mockList1 = mock(List.class); final List mockList2 = mock(List.class); mockList1.get(0); mockList1.get(1); mockList2.get(0); mockList1.get(2); mockList2.get(1); final InOrder inOrder1 = inOrder(mockList1, mockList2); inOrder1.verify(mockList1).get(0); inOrder1.verify(mockList1).get(2); inOrder1.verify(mockList2).get(1); } 五、确保交互(interaction)操作不会执行在 mock 对象上

一些用户可能会在频繁地使用 verifyNoMoreInteractions (),甚至在每个测试函数中都用。但是 verifyNoMoreInteractions () 并不建议在每个测试函数中都使用;

verifyNoMoreInteractions () 在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时;

@Test public void test5() { // 验证某个交互是否从未被执行 final List mock = mock(List.class); mock.add("first"); verify(mock, never()).add("test5"); //通过 verify(mock, never()).add("first"); //异常 // 验证mock对象没有交互过 final List mock1 = mock(List.class); final List mock2 = mock(List.class); verifyZeroInteractions(mock1); //通过 verifyNoMoreInteractions(mock1, mock2); //通过 verifyZeroInteractions(mock, mock2); //异常 // 注意:可能只想验证前面的逻辑,但是加上最后一行,会导致出现异常。建议使用方法层面的验证,如:never(); // 在验证是否有冗余调用的时候,可使用此种方式。如下: final List mockList = mock(List.class); mockList.add("one"); mockList.add("two"); verify(mockList).add("one"); // 通过 verify(mockList, never()).get(0); //通过 verifyZeroInteractions(mockList); //异常 } 六、使用注解简化 mock 对象创建

注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者 test runner 中:

MockitoAnnotations.initMocks(this); 也可以使用内置的 runner: MockitoJUnitRunner 或者一个 rule : MockitoRule; // 代替 mock(ArgumentTestService.class) 创建mock对象; @Mock private ArgumentTestService argumentTestService; // 若改注解修饰的对象有成员变量,@Mock定义的mock对象会被自动注入; @InjectMocks private MockitoAnnotationServiceImpl mockitoAnnotationService; @Test public void test6() { // 注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中; MockitoAnnotations.initMocks(this); when(argumentTestService.argumentTestMethod(new ArgumentTestRequest())).thenReturn("success"); System.out.println(argumentTestService.argumentTestMethod(new ArgumentTestRequest())); //success System.out.println(mockitoAnnotationService.mockitoAnnotationTestMethod()); //null } 七、监控真实对象(部分 mock)

可以为真实对象创建一个监控 (spy) 对象。当你使用这个 spy 对象时真实的对象也会也调用,除非它的函数被 stub 了;

尽量少使用 spy 对象,使用时也需要小心形式,例如 spy 对象可以用来处理遗留代码;

stub 语法中同样提供了部分 mock 的方法,可以调用真实的方法;完全 mock:上文讲的内容是完全 mock,即创建的 mock 对象与真实对象无关,mock 对象的方法默认都是基本的实现,返回基本类型。可基于接口、实现类创建 mock 对象。部分 mock:所谓部分 mock,即创建的 mock 对象时基于真实对象的,mock 对象的方法都是默认使用真实对象的方法,除非 stub 之后,才会以 stub 为准。基于实现类创建 mock 对象,否则在没有 stub 的情况下,调用真实方法时,会出现异常。注意点:Mockito 并不会为真实对象代理函数调用,实际上它会拷贝真实对象。因此如果你保留了真实对象并且与之交互,不要期望从监控对象得到正确的结果。 当你在监控对象上调用一个没有被 stub 的函数时并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果

@Test public void test7() { // stub部分mock(stub中使用真实调用)。注意:需要mock实现类,否则会有异常 final StubTestService stubTestService = mock(StubTestServiceImpl.class); when(stubTestService.stubTestMethodA("paramA")).thenCallRealMethod(); doCallRealMethod().when(stubTestService).stubTestMethodB(); System.out.println(stubTestService.stubTestMethodA("paramA")); //stubTestMethodA is called, param = paramA System.out.println(stubTestService.stubTestMethodB()); //stubTestMethodB is called System.out.println(stubTestService.stubTestMethodC()); //null // spy部分mock final LinkedList linkedList = new LinkedList(); final LinkedList spy = spy(linkedList); spy.add("one"); spy.add("two"); doReturn(100).when(spy).size(); when(spy.get(0)).thenReturn("one_test"); System.out.println(spy.size()); //100 System.out.println(spy.get(0)); //one_test System.out.println(spy.get(1)); //two // spy可以类比AOP。在spy中,由于默认是调用真实方法,所以第二种写法不等价于第一种写法,不推荐这种写法。 doReturn("two_test").when(spy).get(2); when(spy.get(2)).thenReturn("two_test"); //异常 java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 System.out.println(spy.get(2)); //two_test // spy对象只是真实对象的复制,真实对象的改变不会影响spy对象 final List arrayList = new ArrayList(); final List spy1 = spy(arrayList); spy1.add(0, "one"); System.out.println(spy1.get(0)); //one arrayList.add(0, "list1"); System.out.println(arrayList.get(0)); //list1 System.out.println(spy1.get(0)); //one // 若对某个方法stub之后,又想调用真实的方法,可以使用reset(spy) final ArrayList arrayList1 = new ArrayList(); final ArrayList spy2 = spy(arrayList1); doReturn(100).when(spy2).size(); System.out.println(spy2.size()); //100 reset(spy2); System.out.println(spy2.size()); //0 } 八、 @Mock和*@Spy* 的使用

@Mock等价于 Mockito.mock (Object.class);

@Spy等价于 Mockito.spy (obj);

区分是mock对象还是spy对象: Mockito.mockingDetails(someObject).isMock(); Mockito.mockingDetails(someObject).isSpy(); @Mock private StubTestService stubTestService; @Spy private StubTestServiceImpl stubTestServiceImpl; @Spy private StubTestService stubTestServiceImpl1 = new StubTestServiceImpl(); @Test public void test8() { MockitoAnnotations.initMocks(this); // mock对象返回默认 System.out.println(stubTestService.stubTestMethodB()); //null // spy对象调用真实方法 System.out.println(stubTestServiceImpl.stubTestMethodC()); //stubTestMethodC is called System.out.println(stubTestServiceImpl1.stubTestMethodA("spy")); //stubTestMethodA is called, param = spy // 区分是mock对象还是spy对象 System.out.println(mockingDetails(stubTestService).isMock()); //true System.out.println(mockingDetails(stubTestService).isSpy()); //false System.out.println(mockingDetails(stubTestServiceImpl).isSpy()); //true } 九、ArgumentCaptor(参数捕获器)捕获方法参数进行验证。(可代替参数匹配器使用)

在某些场景中,不光要对方法的返回值和调用进行验证,同时需要验证一系列交互后所传入方法的参数。那么我们可以用参数捕获器来捕获传入方法的参数进行验证,看它是否符合我们的要求。ArgumentCaptor 介绍通过 ArgumentCaptor 对象的 forClass (ClassArgumentCaptor 的 Apiargument.capture () 捕获方法参数argument.getValue () 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值argument.getAllValues () 方法进行多次调用后,返回多个参数值

@Test public void test9() { List mock = mock(List.class); List mock1 = mock(List.class); mock.add("John"); mock1.add("Brian"); mock1.add("Jim"); // 获取方法参数 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); verify(mock).add(argument.capture()); System.out.println(argument.getValue()); //John // 多次调用获取最后一次 ArgumentCaptor argument1 = ArgumentCaptor.forClass(String.class); verify(mock1, times(2)).add(argument1.capture()); System.out.println(argument1.getValue()); //Jim // 获取所有调用参数 System.out.println(argument1.getAllValues()); //[Brian, Jim] } 十、简化 ArgumentCaptor 的创建 @Mock private List captorList; @Captor private ArgumentCaptor argumentCaptor; @Test public void test10() { MockitoAnnotations.initMocks(this); captorList.add("cap1"); captorList.add("cap2"); System.out.println(captorList.size()); verify(captorList, atLeastOnce()).add(argumentCaptor.capture()); System.out.println(argumentCaptor.getAllValues()); } 十一、高级特性:自定义验证失败信息 @Test public void test11() { final ArrayList arrayList = mock(ArrayList.class); arrayList.add("one"); arrayList.add("two"); verify(arrayList, description("size()没有调用")).size(); // org.mockito.exceptions.base.MockitoAssertionError: size()没有调用 verify(arrayList, timeout(200).times(3).description("验证失败")).add(anyString()); //org.mockito.exceptions.base.MockitoAssertionError: 验证失败 } 十二、高级特性:修改没有测试桩的调用的默认返回值

可以指定策略来创建 mock 对象的返回值。这是一个高级特性,通常来说,你不需要写这样的测试;

它对于遗留系统来说是很有用处的。当你不需要为函数调用打桩时你可以指定一个默认的 answer;

@Test public void test12(){ // 创建mock对象、使用默认返回 final ArrayList mockList = mock(ArrayList.class); System.out.println(mockList.get(0)); //null // 这个实现首先尝试全局配置,如果没有全局配置就会使用默认的回答,它返回0,空集合,null,等等。 // 参考返回配置:ReturnsEmptyValues mock(ArrayList.class, Answers.RETURNS_DEFAULTS); // ReturnsSmartNulls首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回SmartNull。 // 如果最终返回对象,那么会简单返回null。一般用在处理遗留代码。 // 参考返回配置:ReturnsMoreEmptyValues mock(ArrayList.class, Answers.RETURNS_SMART_NULLS); // 未stub的方法,会调用真实方法。 // 注1:存根部分模拟使用时(mock.getSomething ()) .thenReturn (fakeValue)语法将调用的方法。对于部分模拟推荐使用doReturn语法。 // 注2:如果模拟是序列化反序列化,那么这个Answer将无法理解泛型的元数据。 mock(ArrayList.class, Answers.CALLS_REAL_METHODS); // 深度stub,用于嵌套对象的mock。参考:https://www.cnblogs.com/Ming8006/p/6297333.html mock(ArrayList.class, Answers.RETURNS_DEEP_STUBS); // ReturnsMocks首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回mock。 // 如果返回类型不能mocked(例如是final)然后返回null。 mock(ArrayList.class, Answers.RETURNS_MOCKS); // mock对象的方法调用后,可以返回自己(类似builder模式) mock(ArrayList.class, Answers.RETURNS_SELF); // 自定义返回 final Answer answer = new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return "test_answer"; } }; final ArrayList mockList1 = mock(ArrayList.class, answer); System.out.println(mockList1.get(0)); //test_answer } 十三、相关学习资料

Mockito 英文版 javadoc:https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html

Mockito 中文文档 (部分):单元测试利器-Mockito 中文文档_mockito官网文档-CSDN博客

Mockito 使用教程:Mockito教程 - 明-Ming - 博客园

参数捕获器使用:https://www.journaldev.com/21892/mockito-argumentcaptor-captor-annotation

利用 ArgumentCaptor(参数捕获器)捕获方法参数进行验证:https://www.iteye.com/blog/hotdog-916364

改变 mock 返回值:Mockito中RETURNS_DEEP_STUBS对于enum的处理 – 夜明的孤行灯

五分钟了解 Mockito:https://www.iteye.com/blog/liuzhijun-1512780

使用 Mockito 进行单元测试:https://www.iteye.com/blog/qiuguo0205-1443344

JUnit + Mockito 单元测试:JUnit + Mockito 单元测试(二)_invocationonmock-CSDN博客

Mockito 中*@Mock* 与*@InjectMock: href="https://www.cnblogs.com/langren1992/p/9681600.html"title="https://www.cnblogs.com/langren1992/p/9681600.html">https://www.cnblogs.com/langren1992/p/9681600.html*

mockito 中两种部分 mock 的实现,spy、callRealMethod:mockito中两种部分mock的实现,spy、callRealMethod - 沧海一滴 - 博客园

Mockito 中被 Mocked 的对象属性及方法的默认值:Mockito 中被 Mocked 的对象属性及方法的默认值 - Boblim - 博客园

单元测试工具之 Mockito:单元测试工具之Mockito_mockito作用-CSDN博客

引入 Mockito 测试用@Spy** 和*@Mock: href="https://blog.csdn.net/message_lx/article/details/83308114"title="https://blog.csdn.net/message_lx/article/details/83308114">https://blog.csdn.net/message_lx/article/details/83308114***

Mockito 初探 (含实例):https://www.iteye.com/blog/sgq0085-2031319

测试覆盖率统计:在idea中使用JaCoCo插件统计单元测试覆盖率_idea跑单元测试caused by: java.ang .runtimeexception: cl-CSDN博客

****测试覆盖率无法统计解决:[idea 单元测试 覆盖率不显示问题,no coverage result_idea no coverage results-CSDN博客

df0850dc33f8da276663b68e19dc0b8d.jpeg

52fafdb5a396503b2bf9168e73ab7502.png

微信搜一搜 或 长按关注我‍‍

708422354db3d84e4eb17d44d5a26472.png

开源优测



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有