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 参考
github mockito/mockito: Most popular Mocking framework for unit tests written in Java (github.com)