Mock测试利器mockito的使用

    技术2022-07-11  90

    Mockito简介

    Mockito简介摘自官网,Mockito是一个非常优秀mock框架。他用简单易用的API让单元测试的编写更加简洁优雅。Mockito测试用例可读性高,校验规则清晰。Mockito社区活跃,StackOverflow投票Mockito是最受欢迎的java mock框架。

    常用术语

    mock 在测试过程中有许多难以构建的对象如何HttpServletRequest需要依赖Servlet容器,此时就可以使用mock出来一个虚拟的对象,来测试他的方法stub 打标 简单的讲就是伪造一个方法,修改原来方法的调用,返回一个伪造的自定义值verify 验证 mockito中对操作行为的验证 如mock的对象的add 操作之后是clear操作,verify的顺序也要是add 和 clearwhen then give 等介词是一种BDD(behavior driver delopment)行为驱动开发中的表示条件,断言等状态的介词

    Mockito入门

    添加maven依赖

    <dependencies> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.3.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>

    基于JunitRunner来运行Mockito

    核心利用MockitoJunitRunner来跑Junit的单元测试@RunWith(MockitoJUnitRunner.class)

    // 使用Junit的MockitoRunner开启Mockito测试 @RunWith(MockitoJUnitRunner.class) public class SimpleUseMockito { /** * 基于Mockito的校验操作行为 */ @Test public void testSimpleVerifyBehavior(){ // 使用mockito的静态方法生成一个mock 对象 ArrayList<Integer> list = mock(ArrayList.class); System.out.println(list.getClass()); // mock出来的对象的一些行为 list.add(0); list.add(1); list.clear(); // 使用mockito 的verify 方法来校验操作步骤 verify(list).add(0); verify(list).add(1); verify(list).clear(); } }

    基于Annation来运行Mockito

    使用MockitoAnnotations.init(testClass) 来初始化一个需要mock的类

    @Mock 注解来表示该对象是mock出来的

    public class UseMockitoByAnnotation { @Mock List<Integer> list; @Before public void init() { MockitoAnnotations.initMocks(this); } @Test public void test1() { doReturn(1).when(list).get(0); assertThat(list.get(0), equalTo(1)); } @After public void destroy() { } }

    基于Junit Rule 运行Mockito

    MockitoJunit.rule()创建一个Junit的Rule 其他使用方式与注解和Runner类似

    public class UseMockitoByRule { @Rule public MockitoRule mockitorule = MockitoJUnit.rule(); @Mock List<Integer> list; @Test public void test1() { doReturn(1).when(list).get(0); assertThat(list.get(0), equalTo(1)); } }

    verify 行为验证

    /** * 基于Mockito的校验操作行为 */ @Test public void testSimpleVerifyBehavior(){ // 使用mockito的静态方法生成一个mock 对象 ArrayList<Integer> list = mock(ArrayList.class); System.out.println(list.getClass()); // mock出来的对象的一些行为 list.add(0); list.add(1); list.clear(); // 使用mockito 的verify 方法来校验操作步骤 这个对顺序没有要求 verify(list).add(0); verify(list).add(1); verify(list).clear(); }

    Verify 验证带次数

    @Test public void testVerifyByTime() { ArrayList<Integer> list = mock(ArrayList.class); list.add(1); list.add(2); list.add(2); list.add(3); list.add(3); list.add(3); // time(int) 调用次数验证 verify(list, times(1)).add(1); verify(list, times(2)).add(2); verify(list, times(3)).add(3); // never() 一次都没调用 verify(list, never()).add(4); // atLeastOnce 至少一次 atMostOnce 最多一次 // atLeast(int) 至少X次 atMost(int) 最多X次 verify(list, atLeastOnce()).add(1); verify(list, atMostOnce()).add(1); verify(list, atLeastOnce()).add(2); verify(list, atLeast(2)).add(2); verify(list, atLeast(3)).add(3); verify(list, atMost(5)).add(3); }

    Verify 验证带顺序

    public void testVerifyInOrder() { // 单个mock对象 ArrayList<Integer> single = mock(ArrayList.class); single.add(1); single.add(2); InOrder inOrder = inOrder(single); // 被抛出VerificationInOrderFailure 的Error // inOrder.verify(single).add(2); // inOrder.verify(single).add(1); inOrder.verify(single).add(1); inOrder.verify(single).add(2); // 多个mock对象 ArrayList<Integer> mul1 = mock(ArrayList.class); ArrayList<Integer> mul2 = mock(ArrayList.class); mul1.add(1); mul2.add(2); InOrder inOrderMul = inOrder(mul1,mul2); // 必须是mul1调用add(1) 在mul2调用add(2)之前 inOrderMul.verify(mul1).add(1); inOrderMul.verify(mul2).add(2); }

    VerifyNoInterations 验证类没有被调用过

    @Test public void testVerifyNeverInvoke(){ // 多个mock对象 ArrayList<Integer> mul1 = mock(ArrayList.class); ArrayList<Integer> mul2 = mock(ArrayList.class); mul1.add(1); verify(mul1).add(1); // 验证没有调用过 verifyNoInteractions(mul2); }

    Stub基础使用

    // 最简单的stub @Test public void simpleStubbing() { // mock 一个 list对象 ArrayList<Integer> list = mock(ArrayList.class); // 使用stubbing 伪造list.get(0)的结果返回0 when(list.get(0)).thenReturn(0); // 调用list.get(0) 然后去断言 通过 其实本身这个list是没有数据的 通过mockito Stub了一个伪造的数据 assertThat(list.get(0),equalTo(0)); } // 带有异常的stubbing @Test public void simpleExceptionStubbing(){ ArrayList<Integer> list = mock(ArrayList.class); // 使用thenThrow 抛出一个异常 when(list.get(0)).thenThrow(new RuntimeException()); try { list.get(0); fail(); } catch (Exception e) { assertThat(e,instanceOf(RuntimeException.class)); } } // doXXX的Stub // 用于stubbing 没有返回值的可以使用doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 这几个方法 后面接when(需要Stub对象).xxx方法() @Test public void useDoXXStubbing() { // 使用doXX可以mock没有返回值的请求 ArrayList<Integer> list = mock(ArrayList.class); // 使用doReturn 实现与 thenReturn 类似效果 doReturn(1).when(list).get(0); assertThat(list.get(0), equalTo(1)); // 使用doThrow 实现 与 thenThrow类似效果 doThrow(new UnsupportedOperationException()).when(list).clear(); try { list.clear(); fail(); } catch (Exception e) { assertThat(e, instanceOf(UnsupportedOperationException.class)); } } /** * 使用Answer 高度定制Stub */ @Test public void testAnswer(){ ArrayList<Integer> list = mock(ArrayList.class); doAnswer(invocation -> { // invocation 中可以获取方法的参数 方法 mock对象 或者调用真实的方法 Object arg1 = invocation.getArgument(0); System.out.println(arg1); return 1; }).when(list).get(0); assertThat(list.get(0), equalTo(1)); }

    连续Stubbing

    @Test public void testStubbingConsecutive(){ ArrayList<Integer> list = mock(ArrayList.class); // 调用的调用thenXXX进行Stubbing when(list.get(0)).thenReturn(100).thenThrow(new UnsupportedOperationException()); assertThat(list.get(0),equalTo(100)); try { list.get(0); } catch (Exception e) { assertThat(e,instanceOf(UnsupportedOperationException.class)); } }

    mockito调用真实方法

    // 调用真实的方法 public class TestService { private final InnerTestService innerTestService; public TestService(InnerTestService innerTestService) { this.innerTestService = innerTestService; } public Integer test1() { System.out.println("really do test1"); return 1; } public void test2(String val) { System.out.println("really do test2 " + val); } } public class InnerTestService { } @Test public void testRealCallMethod(){ TestService testService = mock(TestService.class); // 调用的mockito代理出来的mock方法不会调用真实的方法 不会打印 really do test1 when(testService.test1()).thenReturn(1); assertThat(testService.test1(), equalTo(1)); // 申明 testService.test或调用真的testService 方法 控制台会打印 really do test1 when(testService.test1()).thenCallRealMethod(); assertThat(testService.test1(), equalTo(1)); }

    mockito deep mock

    // deep mock 深度mock @Test public void testDeepMock() { // deep mock 表示深度mock 对象里面包含的其他对象 TestService noDeepMock = mock(TestService.class); try { // 这里调用被抛出NPE noDeepMock.testInnerTest().test(); } catch (Exception e) { // 断言exception assertThat(e, instanceOf(NullPointerException.class)); } // 使用 deep stub方法 mockito会将 对象中关联依赖的对象一并mock 不会出现 NoPointException TestService testService = mock(TestService.class, Answers.RETURNS_DEEP_STUBS); when(testService.testInnerTest().test()).thenReturn(1); assertThat(testService.testInnerTest().test(), equalTo(1)); }

    mockito spy的使用

    可以创建一个真实对象的spy,当使用spy对象的时候会真正调用真实对象的方法(除非改方法被stub)

    /** * 可以创建一个真实对象的spy,当使用spy对象的时候会真正调用真实对象的方法(除非改方法被stub) * 跟mock不同的是,利用Spy可以做到部分的mock,只在需要mock的部分做stubbing即可完成mock */ @Test public void testSpy() { // 1. 创建真实的对象 List<Integer> realList = new ArrayList<>(); // 调用spy传入真实对象返回改对象的SPY List<Integer> spy = spy(realList); // 真实调用方法 spy.add(1); spy.add(2); spy.add(3); assertThat(spy.size(), equalTo(3)); // 如果需要mock处理 做一个特殊的stubbing 即可 when(spy.get(0)).thenReturn(100); assertThat(spy.get(0), equalTo(100)); }

    spy真实对象时候的坑

    在spy调用真实对象的时候可能会出现异常,此时用doXXX方法来解决

    @Test public void testSpyGotcha() { // 1. 创建真实的对象 List<Integer> realList = new ArrayList<>(); // 调用spy传入真实对象返回改对象的SPY List<Integer> spy = spy(realList); try { // 因为会调用真实对象 所有spy.get(0) 会报索引越界 when(spy.get(0)).thenReturn(1); } catch (Exception e) { assertThat(e, instanceOf(IndexOutOfBoundsException.class)); } // 使用doReturn 来解决这个问题 doReturn(1).when(spy).get(0); assertThat(spy.get(0),equalTo(1)); }

    修改没有被stub方法默认的返回值规则

    Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS); Foo mockTwo = mock(Foo.class, new YourOwnAnswer());

    mockito 参数捕获 ArgumentCaptor

    mockito在做测试时候有时候不仅需要对测试的返回值进行断言,有时也会对调用的方法参数传入的具体参数值进行断言,此时可以使用ArgumentCaptor来捕获参数值

    需要特别注意的是ArgumentCaptor只能用在verfication的时候不能使用在stub中,在stubbing中使用ArgumentCaptor会减少测试的可靠性,因为ArgumentCaptor是在assert块之外创建的,也会因为stubbing的方法没有调用导致没有参数可以捕获导致不好定位错误。

    /** * argument.capture() 捕获方法参数 * argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值 * argument.getAllValues() 方法进行多次调用后,返回多个参数值 */ @Test public void testArgumentCapturing() { ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class); List<String> list1 = mock(List.class); List<String> list2 = mock(List.class); list1.add("jack"); list2.add("tom"); list2.add("tomcat"); // 在verify中使用 使用ArgumentCaptor 捕获传入的参数 verify(list1, times(1)).add(argumentCaptor.capture()); assertThat("jack", equalTo(argumentCaptor.getValue())); verify(list2, times(2)).add(argumentCaptor.capture()); assertArrayEquals(new String[]{"jack", "tom", "tomcat"}, argumentCaptor.getAllValues().toArray()); }

    Mockito 对BDD的别名支持

    BDD ( Behavior Driver Delopment )基于行为驱动开发,具体参考wiki或者其他关于BDD的相关资料

    @Test public void testSupport4BDD(){ List<Integer> mock = mock(List.class); // give given(mock.get(0)).willReturn(100); // when Integer result = mock.get(0); // then assertThat(result,equalTo(100)); }

    Mockito对Serializable的支持

    mock可以被序列化,使用mockito的序列化支持可以在一些依赖需要序列化的时候进行mock

    List serializableMock = mock(List.class, withSettings().serializable());

    Mockito对final Class finalMethods enum的mock支持 (2.1.0之后)

    使用方法在classpath下创建一个mockito-extensions目录创建一个org.mockito.plugins.MockMaker文件内容为mock-maker-inline即可mock final类型

    Mockito中参数统配

    使用过程中需要注意参数统配的顺序,范围越大的统配放在后面会覆盖范围具体的统配条件

    @Test public void testArgument() { MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class); // stub anyString传入任意String anyInt任意int anyCollection任意collection的子类 any()任意一个class值保证编译不报错 when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("test arg"); assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("test arg")); assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("test arg")); } /** * 部分统配 部分特殊定制值eq()方法 */ @Test public void testWildCardArgument() { MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class); // stub 中其中某一些参数需要指定 则用到eq 如果不使用eq直接使用会报 InvalidUseOfMatchersException when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1"); when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2"); assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq 1")); assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq 2")); } /** * 统配的使用顺序 */ @Test public void testWildCardArgumentOrderErorr() { MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class); // 在使用统配参数的时候需要注意使用的顺序 必须把通用的放在之前 如果把通用的放在最后会覆盖之前特别定制的参数 when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1"); when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2"); // 统配在最后 会覆盖之前特殊处理的stub when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("eq anyInt"); assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq anyInt")); assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq anyInt")); } /** * 统配的使用顺序 */ @Test public void testWildCardArgumentOrderSuccess() { MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class); // 在使用统配参数的时候需要注意使用的顺序 必须把通用的放在之前 如果把通用的放在最后会覆盖之前特别定制的参数 // 统配的在之前 正常匹配特殊定制的 when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("eq anyInt"); when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1"); when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2"); assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq 1")); assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq 2")); }
    Processed: 0.013, SQL: 9