Mockito 学习笔记

1 关于 Mockito

1.1 简介

Mockito 是一个 java mock 框架,主要用于代码的 mock 测试。

在真实的开发环境里,Mockito 可以阻断依赖链条,达到只测试某个方法内代码的目的。

举个例子:
AService.someMethod1(...) 里使用了 BService.someMethod(...) 和 AService.someMethod2(...) 这两个方法。
当开发者只想要测试 AService.someMethod1(...) 的时候,
就可以通过 mock 框架模拟 BService.someMethod(...) 和 AService.someMethod2(...),
以此来达到只测试 AService.someMethod1(...) 的目的。

Mockito 除了服务端代码的 mock,还可以 mock 安卓代码。

本文只考虑 java 服务端开发部分,暂不涉及安卓开发。

1.2 Mockito 版本

Mockito 到目前为止一共 5 个大版本更新

  • Mockito 1.x -- 维护时间 2008 - 2014 年
  • Mockito 2.x -- 重构了 api,当前主流版本之一,维护时间 2015 - 2019 年
  • Mockito 3.x -- 在 2 的基础上没有大改 api,但是兼容了 jdk8,维护时间 2019 - 2021 年
  • Mockito 4.x -- 删除了一些过时的 api,维护时间 2021 - 2022 年
  • Mockito 5.x -- 需要 jdk11 及以上的项目使用,维护时间 2023 年以后

1.3 PowerMock

PowerMock 是一个 mock 门面框架,它可以补充 EasyMock 或者 Mockito 的功能。

Mockito 的原理是对对象进行代理,这种方式的最大问题是没办法对 private 和 static 方法进行处理。

PowerMock 则通过字节码编辑的方式更彻底的处理了对象,使得修改 private 和 static 方法变成了可能。

Mockito3 之后的版本里补全了相关功能,也就用不到 PowerMock 了。

PowerMock 在 2020 年以后就不再维护了。

PowerMock 分为 1.x 和 2.x 版本,一般使用 2.x。

如果项目中使用 Mockito 2.x 或者 3.x 的话,一般需要配置 PowerMock 使用。

注意:

1.4 私有方法的 mock

Mockito 截止 5.10.0 还没有支持 private 方法的 mock,但是 PowerMock 是支持的。

Mockito 的团队认为,private 方法是不需要 mock 的,因为那是需要 mock 的方法的一部分,而不是外部依赖。

当 private 方法需要 mock 的时候,说明代码的编码是有问题的,建议重新进行编码。

如果真的需要 mock private 方法,可以使用 PowerMock 2.x,一般搭配 Mockito 3.x 使用。

静态方法的

1.5 相关包的 Maven 依赖

1.5.1 Mockito 5.x

Mockito 5.10.0 是截止到 2024 年 2 月的最新版本。

Mockito 5.x 需要配合 jdk11 及以上的 jdk 版本使用。

如果使用 Mockito 5.x,则最好使用 5.2.0 以后的版本,不需要单独引入 mockito-inline 包了。




    org.mockito
    mockito-core
    5.10.0
    test





    org.mockito
    mockito-junit-jupiter
    5.10.0
    test

1.5.2 Mockito 4.x

jdk8 下一般使用 Mockito 4.x,api 和 5.x 一致。



    org.mockito
    mockito-core
    4.11.0
    test





    org.mockito
    mockito-inline
    4.11.0
    test




    org.mockito
    mockito-junit-jupiter
    4.11.0
    test

1.5.3 Mockito 3.x

Mockito 3.x 一般配合 PowerMock 2.x 和 JUnit 4.x 使用。

mockito-all 包已经不再更新了。



    org.powermock
    powermock-module-junit4
    2.0.9
    test




    org.powermock
    powermock-api-mockito2
    2.0.9
    test




    org.mockito
    mockito-all
    1.10.19
    test

1.5.4 SpringBoot 依赖

spring-boot-starter-test 里自带了 mockito 相关依赖,可以剔除掉,然后在 pom 里引入想要的版本。

在 SpringBoot 2.x 的版本里,一般引用的 Mockito 4.x,在真实的开发中需要额外引入 mockito-inline 来补充静态方法的 mock 能力。

SpringBoot 2.2.0 之前的版本里使用 JUnit 4.x,之后的版本里使用 JUnit 5.x。


    org.springframework.boot
    spring-boot-starter-test
    test
    
        
            org.mockito
            mockito-core
        
        
            org.mockito
             mockito-junit-jupiter
        
    

2 最佳实践

2.0 Demo

创建 Demo 接口类:

public interface MockClass {

    /**
     * 测试方法 1 - 无入参,有出参
     */
    String test1();

    /**
     * 测试方法 2 - 有入参,有出参
     */
    String test2(String re);

    /**
     * 测试方法 3 - 无入参,有出参
     */
    void test3();

    /**
     * 测试方法 4 - 无入参,无出参
     */
    void test4();
  
    /**
     * 测试静态方法 - 需要一个入参
     */
    static String staticTestMethod(String printData) {
        return "print private test method return " + printData;
    }
}

接口的实现:

public class MockClassImpl implements MockClass {

    private final MockInnerClass innerClass;

    /**
     *     存入 inner class
     */
    public void setInnerClass(MockInnerClass innerClass) {
        this.innerClass = innerClass;
    }

    /**
     * 有参构造器,注入一个 inner 对象
     */
    public MockClassImpl(MockInnerClass innerClass) {
        this.innerClass = innerClass;
    }

    /**
     * 测试方法 1 - 无入参,有出参
     */
    public String test1() {
        return "test1";
    }

    /**
     * 测试方法 2 - 有入参,有出参
     */
    public String test2(String re) {
        return re;
    }

    /**
     * 测试方法 3 - 无入参,有出参
     */
    public void test3() {
        System.out.println("print test3");
    }


    public void test4() {
        System.out.println(MockClass.staticTestMethod("test4"));
    }

    public void testForInner() {
        System.out.println(innerClass.innerTest());
    }
}

inner 对象的实现:

public class MockInnerClass {

    public String innerTest() {
        return "innerTest";
    }
}

2.1 Mockito 5.x Simple Demo

2.1.1 pom

jdk 版本为 11,在 maven pom 里引入 mockito-core 就可以使用。


    org.mockito
    mockito-core
    5.10.0
    test

2.1.2 main

import org.mockito.MockedStatic;
import org.mockito.Mockito;

public class MockTest {

    public static void main(String[] args) {
        // 1 mock 创建一个 MockClassImpl 对象,这个对象是一个代理出来的对象
        // 以下两种方式的效果是一样的:

        // 1.1 使用 mock class 方式创建对象,当 class 有无参构造器的时候可以使用
        // MockClassImpl mockObj = Mockito.mock(MockClassImpl.class);

        // 1.2 使用 spy 方式创建对象
        // 先创建 inner 对象
        MockInnerClass mockOriginInnerObj = new MockInnerClass();
        // 对 inner 对象进行代理
        MockInnerClass mockInnerObj = Mockito.spy(mockOriginInnerObj);
        // 创建主对象
        MockClassImpl mockOriginObj = new MockClassImpl();
        // 存入 inner class
        mockOriginObj.setInnerClass(mockInnerObj);
         // 对主对象进行代理
        MockClassImpl mockObj = Mockito.spy(mockOriginObj);

        // 2 mock 出来的对象所有方法都是空实现的,直接调用不会有任何效果,但是也不会报错
        // 使用 spy 创建出来的对象,不会影响原来的对象的功能
        System.out.println(mockObj.test1()); // 输出 "null"
        System.out.println(mockOriginObj.test1()); // 输出 "test1",说明原来的对象并没有被影响

        // 3 具体 mock 一个方法,此时该对象内的这个方法有实现了
        Mockito.when(mockObj.test1()).thenReturn("mock-test-1");
        System.out.println(mockObj.test1()); // 输出 "mock-test-1"

        // 4 mock 方法的入参可以选择任意字符串都输出同一个结果,也可以选择特定字符串输出某个结果
        // Mockito.any() -- 任意对象
        // Mockito.anyString() -- 任意字符串
        // Mockito.anyInt() / Mockito.anyDouble() / Mockito.anyLong() -- 任意对应类型的数字
        // 其它相关方法不一一列举
        Mockito.when(mockObj.test2(Mockito.anyString())).thenReturn("mock-test-2");
        System.out.println(mockObj.test2("someString")); // 输出 "mock-test-2"
        // 这里确定如果入参是 "someString" 的情况下,就会输出一个不一样的值
        // 如果入参是多个 object 的话,就会都需要用 equals 比较,都符合才会输出这个值
        Mockito.when(mockObj.test2("someString")).thenReturn("mock-test-3");
        System.out.println(mockObj.test2("someString")); // 输出 "mock-test-3"
        // 使用 thenThrow(...) 去 mock 对象的方法后,调用会报错
        // Mockito.when(mockObj.test2("someString2")).thenThrow(new RuntimeException());
        // System.out.println(mockObj.test2("someString2")); // 会报错

        // 5 mock 一个不需要出参的方法
        // test3() 本身会打印一行字符串,但是在调用 doNothing() 之后,就不会有输出了
        Mockito.doNothing().when(mockObj).test3();
        mockObj.test3();
        // 使用 doThrow(...) 去 mock 对象的方法后,调用会报错
        // Mockito.doThrow(new RuntimeException()).when(mockObj).test3();
        // mockObj.test3();

        // 6 mock 一个 private 方法
        try (MockedStatic mockStatic = Mockito.mockStatic(MockClass.class)) {
            mockStatic.when(() -> MockClass.staticTestMethod(Mockito.anyString()))
                    .thenReturn("mock-test-4");
            System.out.println(MockClass.staticTestMethod("test")); // 输出 "mock-test-4"
            mockObj.test4(); // 由于 test4() 方法内调用了这个静态方法,所以此处输出的也是 "mock-test-4"
            mockOriginObj.test4(); // 输出 "test1",说明原来的对象并没有被影响
        }

        mockObj.test4(); // 上述的静态方法的代理已经被关掉了,所以此处输出 "print private test method return mock-test-4"

        // 7 在特定入参下调用原方法
        Mockito.doCallRealMethod().when(mockObj).test3();
        Mockito.when(mockObj.test2(Mockito.anyString())).thenCallRealMethod();
        mockObj.test3(); // 输出 "print test3"
        System.out.println(mockObj.test2("123")); // 输出 "123"

        // 8 内部依赖
        Mockito.when(mockInnerObj.innerTest()).thenReturn("mock-test-5");
        mockObj.testForInner(); // 输出 "mock-test-5"
    }
}

2.2 Mockito 3.x + PowerMock 2.x + JUnit 4.x

2.2.1 pom

jdk 版本为 8,在 maven pom 里引入 Mockito、PowerMock、JUnit 等。


    org.powermock
    powermock-module-junit4
    2.0.9
    test



    org.powermock
    powermock-api-mockito2
    2.0.9
    test



    org.mockito
    mockito-all
    1.10.19
    test



    junit
    junit
    4.12
    compile

2.2.2 main

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

/**
 * @RunWith(PowerMockRunner.class) 使用 PowerMock 自带的 Runner 跑代码,会自动 spy 代理一些类
 * @PrepareForTest({MockClass.class}) 所有需要代理的静态对象
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({MockClass.class})
public class MockTest {

    /**
     * @InjectMocks 注解和使用 new 没有本质区别
     */
    @InjectMocks
    private MockClassImpl mockObj = new MockClassImpl();

    /**
     * @Mock 注解会自动使用 spy 方法代理这个对象
     * 所以此处的 MockClass 对象已经是代理后的对象了
     */
    @Mock
    private MockInnerClass mockInnerObj;

    @Test
    public void testMainMethod() throws Exception {

        // 1 在不进行任何 mock 的情况下,是可以正常输出原来的结果的
        System.out.println(mockObj.test1()); // 输出 "test1"

        // 2 如果要 mock 整体方法,需要在代码中 spy 对象
        mockObj = PowerMockito.spy(mockObj);
        Mockito.when(mockObj.test1()).thenReturn("mock-test-1");
        System.out.println(mockObj.test1()); // 输出 "mock-test-1"

        // 3 mock 静态方法
        PowerMockito.mockStatic(MockClass.class);
        PowerMockito.when(MockClass.staticTestMethod(Mockito.anyString())).thenReturn("mock-test-4");
        System.out.println(MockClass.staticTestMethod("test")); // 输出 "mock-test-4"
        mockObj.test4(); // 输出 "mock-test-4"

        // 4 mock 内部依赖
        Mockito.when(mockInnerObj.innerTest()).thenReturn("mock-test-5");
        mockObj.testForInner(); // 输出 "mock-test-5"

        // 5 mock 私有方法
        PowerMockito.when(mockObj, "privateMethod", Mockito.anyString()).thenReturn("mock-test-6");
        mockObj.testForPrivate("test"); // 输出 "mock-test-6"
    }
}

2.3 Mockito 5.x + JUnit 5.x

2.3.1 pom

在 maven pom 里引入 Mockito、JUnit 等。


    org.mockito
    mockito-junit-jupiter
    5.10.0
    test



    org.mockito
    mockito-core
    5.10.0
    test



    org.junit.jupiter
    junit-jupiter
    5.8.2
    compile

2.3.2 main

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

/**
 * @ExtendWith 在 JUnit 5 里取代了 @RunWith 注解,用于做项目启动的时候的扩展
 */
@ExtendWith(value = MockitoExtension.class)
public class MockTest {

    /**
     * @InjectMocks 注解和使用 new 没有本质区别
     */
    @InjectMocks
    private MockClassImpl mockObj = new MockClassImpl();

    /**
     * @Mock 注解会自动使用 spy 方法代理这个对象
     * 所以此处的 MockClass 对象已经是代理后的对象了
     */
    @Mock
    private MockInnerClass mockInnerObj;

    @Test
    public void testMainMethod() throws Exception {

        // 1 在不进行任何 mock 的情况下,是可以正常输出原来的结果的
        System.out.println(mockObj.test1()); // 输出 "test1"

        // 2 如果要 mock 整体方法,需要在代码中 spy 对象
        mockObj = Mockito.spy(mockObj);
        Mockito.when(mockObj.test1()).thenReturn("mock-test-1");
        System.out.println(mockObj.test1()); // 输出 "mock-test-1"

        // 3 mock 静态方法
        try (MockedStatic mockStatic = Mockito.mockStatic(MockClass.class)) {
            mockStatic.when(() -> MockClass.staticTestMethod(Mockito.anyString()))
                    .thenReturn("mock-test-2");
            System.out.println(MockClass.staticTestMethod("test")); // 输出 "mock-test-2"
            mockObj.test4(); // 由于 test4() 方法内调用了这个静态方法,所以此处输出的也是 "mock-test-2"
        }

        // 4 mock 内部依赖
        Mockito.when(mockInnerObj.innerTest()).thenReturn("mock-test-3");
        mockObj.testForInner(); // 输出 "mock-test-3"
    }
}

3 参考

官方网站 Mockito framework site

github mockito/mockito: Most popular Mocking framework for unit tests written in Java (github.com)

文档 Mockito (Mockito 5.10.0 API) (javadoc.io)

你可能感兴趣的:(Mockito 学习笔记)