Mock测试利器mockito的使用

Mockito简介

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

常用术语

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

Mockito入门

添加maven依赖

    <dependencies>
        <dependency>
            <groupId>org.mockitogroupId>
            <artifactId>mockito-coreartifactId>
            <version>3.3.3version>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        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"));
    }

你可能感兴趣的:(mockito)