一、Mockito 原理深度解析
1. 什么是 Mockito?
Mockito 是 Java 领域最流行的 Mock 框架之一,主要用于单元测试中模拟(Mock)对象的行为,隔离被测试代码,便于测试。
2. Mock 的本质
Mockito 的核心是通过动态代理(主要是 cglib 和 JDK Proxy)生成对象的 Mock 实例。Mock 实例可以拦截方法调用,并根据预设的行为返回相应的结果。
- 对接口类型,使用 JDK 动态代理。
- 对类类型,使用 cglib 生成子类实现。
3. 主要原理
- 方法拦截:Mock 对象的方法调用会被拦截,转而执行 Mockito 内部的逻辑。
- 行为设定:通过
when(...).thenReturn(...)等语法设定方法调用的返回值或抛出异常。 - 调用验证:通过
verify(...)检查方法是否被调用、调用次数等。
4. 运行流程
- 创建 Mock 对象(
mock(Class)或注解)。 - 设定方法行为(
when(mock.method()).thenReturn(value))。 - 调用 Mock 方法。
- 验证调用(
verify(mock).method())。
二、Mockito 所有注解说明
1. @Mock
- 作用:声明一个 Mock 对象。
- 原理:在测试运行前,Mockito 根据类型生成 Mock 实例并注入到字段。
@Mock
List<String> mockedList;
2. @Spy
- 作用:声明一个 Spy(部分 Mock)对象,真实对象的包装,可以拦截部分方法。
- 原理:创建真实对象,然后用代理包装,未设定的行为走真实方法,设定的走 Mock 行为。
@Spy
List<String> spyList = new ArrayList<>();
3. @InjectMocks
- 作用:自动将
@Mock或@Spy的字段注入到被测试对象中。 - 原理:通过反射自动查找构造函数或字段,将 Mock 实例注入。
@InjectMocks
MyService myService;
4. @Captor
- 作用:声明一个 ArgumentCaptor,用于捕获方法参数。
- 原理:自动创建 ArgumentCaptor 实例。
@Captor
ArgumentCaptor<String> captor;
5. @RunWith(MockitoJUnitRunner.class)
- 作用:让 JUnit 测试运行时自动初始化 Mockito 注解。
- 原理:JUnit Runner 扩展,运行时自动扫描和注入 Mock/Spy 对象。
@RunWith(MockitoJUnitRunner.class)
public class MyTest { ... }
6. @ExtendWith(MockitoExtension.class)(JUnit 5)
- 作用:JUnit 5 下,类似于
@RunWith,自动处理 Mockito 注解。 - 原理:JUnit 5 Extension 机制,自动初始化 Mock/Spy。
@ExtendWith(MockitoExtension.class)
class MyTest { ... }
三、使用原理
- Mock/Spy 创建:通过注解或
Mockito.mock()/Mockito.spy()创建代理对象。 - 行为设定:
when(mock.method()).thenReturn(value)或doReturn(value).when(spy).method()。 - 调用验证:
verify(mock).method(),可指定次数或参数。 - 参数捕获:
ArgumentCaptor捕获调用参数。 - 自动注入:
@InjectMocks自动将 Mock 注入到被测对象依赖。
四、示例代码
1. 基本 Mock 示例
import org.junit.Test;
import org.mockito.Mockito;
import java.util.List;
public class MockitoDemo {
@Test
public void testMock() {
List<String> mockList = Mockito.mock(List.class);
Mockito.when(mockList.get(0)).thenReturn("hello");
System.out.println(mockList.get(0)); // 输出 hello
System.out.println(mockList.get(1)); // 输出 null
Mockito.verify(mockList).get(0); // 验证 get(0) 被调用过
}
}
2. 注解使用示例
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationDemo {
@Mock
List<String> mockList;
@Spy
List<String> spyList = new java.util.ArrayList<>();
@Captor
ArgumentCaptor<String> captor;
@InjectMocks
MyService myService;
@Test
public void testAnnotations() {
Mockito.when(mockList.get(0)).thenReturn("mocked");
spyList.add("real");
Mockito.doReturn("spy-mocked").when(spyList).get(0);
myService.doSomething();
Mockito.verify(mockList).get(0);
Mockito.verify(spyList).get(0);
Mockito.verify(myService).doSomething();
}
}
class MyService {
@Mock
List<String> list;
public void doSomething() {
list.get(0);
}
}
五、常用 API 速查
Mockito.mock(Class)创建 Mock。Mockito.spy(Object)创建 Spy。Mockito.when(...).thenReturn(...)设定行为。Mockito.verify(mock).method()验证调用。ArgumentCaptor<T>捕获参数。MockitoAnnotations.openMocks(this)手动初始化注解(无需 Runner/Extension 时)。
总结
- Mockito 通过动态代理实现 Mock/Spy。
- 注解简化 Mock/Spy/Captor 的声明和注入。
- 行为设定和验证是核心用法。
- 推荐结合 JUnit Runner/Extension 自动初始化注解。
六、深度详解
1、verify(mock, times(n)).method(args);
1.1. 基本原理
verify 是 Mockito 的验证机制,用于断言某个 mock 对象的某个方法,在测试期间被调用了多少次,并且可以验证调用时的参数是否正确。
它不会影响被测代码的执行,只用于测试断言。
1.2. 参数说明
verify(mock, times(n)).method(args);
- mock:你要验证的 mock 对象(通过
mock()创建)。 - times(n):指定期望被调用的次数。常见的有:
times(1):正好调用一次times(0):未被调用times(n):调用 n 次
- method(args):你要验证的方法名和参数。参数可以是具体值,也可以用参数匹配器(如
any()、eq()等)。
1.3. 用途
- 验证某方法是否被调用,调用次数是否正确。
- 验证调用时参数是否符合预期。
- 辅助发现业务逻辑中的遗漏调用或多余调用等问题。
1.4. 常见变体
verify(mock).method(args);
等价于verify(mock, times(1)).method(args);,即正好被调用一次。verify(mock, never()).method(args);
验证方法从未被调用。verify(mock, atLeastOnce()).method(args);
至少被调用一次。verify(mock, atLeast(n)).method(args);
至少被调用 n 次。verify(mock, atMost(n)).method(args);
最多被调用 n 次。
1.5. 参数匹配器
any():匹配任何参数eq(value):匹配等于 value 的参数isNull():匹配 null 参数argThat(...):自定义参数匹配
示例:
verify(mock).method(any());
verify(mock).method(eq("hello"));
1.6. 代码示例
示例 1:验证调用次数和参数
import static org.mockito.Mockito.*;
public class MockitoVerifyDemo {
public interface DemoService {
void doSomething(String param);
}
public static void main(String[] args) {
DemoService mockService = mock(DemoService.class);
// 调用方法
mockService.doSomething("A");
mockService.doSomething("B");
// 验证 doSomething("A") 被调用一次
verify(mockService, times(1)).doSomething("A");
// 验证 doSomething("B") 被调用一次
verify(mockService, times(1)).doSomething("B");
// 验证 doSomething("C") 未被调用
verify(mockService, times(0)).doSomething("C");
}
}
示例 2:参数匹配器
verify(mockService, times(2)).doSomething(anyString());
表示 doSomething 方法被调用了两次,参数是任意字符串。
1.7. 注意事项
verify只能用于 mock 对象,不能用于真实对象。- 如果调用次数不符,测试会失败并抛出异常。
- 如果参数不匹配,测试也会失败。
1.8. 总结
verify(mock, times(n)).method(args);是 Mockito 验证 mock 行为的核心语法。- 用于断言方法的调用次数和参数,保证代码逻辑正确。
- 支持丰富的调用次数和参数匹配方式。
2、Mockito.spy(Object) 参数详解
2.1方法签名
public static <T> T spy(T object)
参数说明
- object:传入的真实对象实例(非 null),用于创建该对象的“部分模拟(Spy)对象”。
- 这个对象可以是任意类型的普通 Java 对象(如 List、业务类等),不是接口类型。
- 不能为 null,否则会抛出异常。
返回值
- 返回一个“Spy对象”,它是原对象的代理(包装),可以拦截部分方法调用。
用途和原理
用途
- 用于部分模拟(Partial Mocking):Spy对象既能调用真实对象的方法,也能对部分方法进行行为设定(即 Mock)。
- 适合场景:你希望在测试中对某些方法进行定制,而其他方法保持真实逻辑。
原理
- Mockito 会对传入的对象创建一个代理(通常是通过 CGLIB),拦截方法调用。
- 未设定的行为,调用真实对象方法。
- 设定的行为(如
doReturn(xxx).when(spy).method(...)),则返回设定值。
使用案例
2.2. 对集合类进行 Spy
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
public class SpyDemo {
public static void main(String[] args) {
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList); // 创建 spy
spyList.add("A"); // 调用真实方法
spyList.add("B");
System.out.println(spyList.size()); // 输出 2
// 对 size() 方法进行 Mock
when(spyList.size()).thenReturn(10);
System.out.println(spyList.size()); // 输出 10
// 其他方法仍然调用真实逻辑
System.out.println(spyList.get(0)); // 输出 "A"
}
}
2.3. 对业务类进行 Spy
public class MyService {
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
import static org.mockito.Mockito.*;
public class SpyBusinessDemo {
public static void main(String[] args) {
MyService realService = new MyService();
MyService spyService = spy(realService);
// 未 mock 方法,调用真实逻辑
System.out.println(spyService.add(2, 3)); // 输出 5
// mock multiply 方法
doReturn(100).when(spyService).multiply(2, 3);
System.out.println(spyService.multiply(2, 3)); // 输出 100
System.out.println(spyService.add(5, 5)); // 输出 10(真实方法)
}
}
2.4. 注意事项
- 不要用 when(spy.method()).thenReturn(xxx) 方式 mock Spy 的非 void 方法(会实际调用一次真实方法),推荐用
doReturn(xxx).when(spy).method(...)。 - Spy 适合原对象有复杂逻辑,只想定制部分方法时使用。
总结
Mockito.spy(Object)的参数是一个真实对象实例。- 返回一个“部分模拟对象”,未设定的方法走真实逻辑,设定的方法走 mock 行为。
- 推荐用
doReturn().when(spy)方式定制方法。 - 适合需要部分 mock 的场景。
3、ArgumentCaptor 作用
ArgumentCaptor 是 Mockito 提供的一个工具类,用于捕获被 mock/stub 方法调用时传入的参数,以便在测试中进行断言和验证。
适用于:
- 方法参数复杂或动态生成,无法直接断言时。
- 需要断言方法调用时传递的参数内容。
3.1、参数与泛型
ArgumentCaptor<T>是一个泛型类,T表示你要捕获的参数类型。- 你可以捕获任意类型参数,包括基本类型、对象、集合等。
3.2、使用原理
- 创建 ArgumentCaptor 对象(如
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class))。 - 在
verify或when时通过 captor 捕获参数。 - 通过 captor 的
getValue()或getAllValues()获取参数值,进行断言。
3.3、典型使用场景
- 验证 mock 方法调用时,参数是否正确。
- 捕获多次调用的参数。
- 复杂对象参数的内容断言。
3.4、代码示例
基本用法
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.mockito.ArgumentCaptor;
import org.junit.Test;
public class ArgumentCaptorDemo {
public interface Service {
void send(String msg);
}
@Test
public void testArgumentCaptor() {
Service mockService = mock(Service.class);
// 调用方法
mockService.send("hello");
// 创建 captor
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
// 验证并捕获参数
verify(mockService).send(captor.capture());
// 获取捕获的参数
String captured = captor.getValue();
assertEquals("hello", captured);
}
}
捕获多个参数
@Test
public void testMultipleArguments() {
Service mockService = mock(Service.class);
mockService.send("first");
mockService.send("second");
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mockService, times(2)).send(captor.capture());
// 获取所有参数
List<String> allArgs = captor.getAllValues();
assertEquals(Arrays.asList("first", "second"), allArgs);
}
捕获复杂对象参数
public class User {
String name;
int age;
// 构造器、getter、setter略
}
public interface UserService {
void addUser(User user);
}
@Test
public void testComplexArgument() {
UserService mockUserService = mock(UserService.class);
mockUserService.addUser(new User("Alice", 20));
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(mockUserService).addUser(captor.capture());
User capturedUser = captor.getValue();
assertEquals("Alice", capturedUser.getName());
assertEquals(20, capturedUser.getAge());
}
注解方式(推荐在 JUnit Runner/Extension下)
@RunWith(MockitoJUnitRunner.class)
public class AnnotationCaptorDemo {
@Mock
Service service;
@Captor
ArgumentCaptor<String> stringCaptor;
@Test
public void testCaptorAnnotation() {
service.send("hi");
verify(service).send(stringCaptor.capture());
assertEquals("hi", stringCaptor.getValue());
}
}
3.5、常用方法
capture():在 verify 或 when 时用来捕获参数。getValue():获取最近一次捕获的参数值。getAllValues():获取所有捕获的参数值(适用于多次调用)。
3.6、注意事项
- ArgumentCaptor 必须用于 mock 对象的方法(不能用于真实对象)。
- 捕获参数时,verify 必须与 captor.capture() 配合。
- 可以捕获多个参数和复杂类型参数。
3.7、总结
ArgumentCaptor<T>用于捕获并断言 mock 方法调用时的参数。- 支持单参数、多参数、复杂对象参数。
- 断言参数内容,提升测试覆盖率和准确性。
4、MockitoAnnotations.openMocks(this)
4.1、作用
MockitoAnnotations.openMocks(this) 是 Mockito 3.x 及以上版本推荐的注解初始化方法,用于自动扫描并初始化当前类中的 Mockito 注解(如 @Mock、@Spy、@Captor、@InjectMocks),从而让这些字段生效,无需使用 JUnit Runner 或 Extension。
4.2、参数详解
方法签名
public static AutoCloseable openMocks(Object testClass)
- testClass:通常是测试类的实例(如
this),即需要扫描注解并初始化的对象。 - 返回值:
AutoCloseable,用于自动资源释放(如与 try-with-resources 配合使用)。
4.3、原理
- 扫描 testClass 的字段,查找 Mockito 注解(@Mock、@Spy、@Captor、@InjectMocks)。
- 自动创建 mock/spy/captor 实例,并通过反射赋值到对应字段。
- 支持依赖注入(@InjectMocks),将已初始化的 mock/spy 注入到被测对象。
- 等价于 JUnit4 的
@RunWith(MockitoJUnitRunner.class)或 JUnit5 的@ExtendWith(MockitoExtension.class),但不依赖测试框架 Runner/Extension。
4.4、典型使用场景
- 测试类中使用 Mockito 注解,但不想或不能用 Runner/Extension。
- 与 TestNG、普通 Java main 方法等场景结合使用。
- 需要手动控制 Mockito 注解初始化时机。
4.5、代码示例
1. 基本用法
import org.junit.Before;
import org.junit.Test;
import org.mockito.*;
public class OpenMocksDemo {
@Mock
private List<String> mockList;
@Spy
private ArrayList<String> spyList;
@Captor
private ArgumentCaptor<String> stringCaptor;
@InjectMocks
private MyService myService;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testMockInit() {
Mockito.when(mockList.size()).thenReturn(10);
spyList.add("spy");
Mockito.doReturn("mocked").when(spyList).get(0);
myService.process();
// 验证和断言
Mockito.verify(mockList).size();
Mockito.verify(spyList).add(stringCaptor.capture());
assertEquals("spy", stringCaptor.getValue());
}
}
2. 与 try-with-resources 配合(推荐)
这样可以自动关闭资源,避免潜在的线程泄漏。
import org.mockito.MockitoAnnotations;
public class TryWithResourcesDemo {
@Mock
private List<String> mockList;
@Before
public void setUp() throws Exception {
try (AutoCloseable mocks = MockitoAnnotations.openMocks(this)) {
// 测试代码...
}
}
}
4.6、注意事项
- 必须在测试方法执行前调用(如在
@Before方法中)。 - 只初始化当前对象的字段,不会递归初始化嵌套类或父类字段。
- 返回值需关闭:如果用 try-with-resources,需关闭以释放资源。
- 与 @RunWith/@ExtendWith 不可混用,否则可能重复初始化。
- 适用于 TestNG、普通 Java 测试、Spring Test 等多种场景。
4.7、总结
MockitoAnnotations.openMocks(this)用于自动初始化 Mockito 注解字段。- 适合不使用 JUnit Runner/Extension 的场景。
- 支持 try-with-resources 自动资源管理。
- 推荐在
@Before方法中调用,确保 mock/spy/captor 等字段初始化。
5、assert使用
JUnit 提供了静态断言方法(org.junit.Assert,JUnit 4;org.junit.jupiter.api.Assertions,JUnit 5),常用参数如下:
5.1. 常用断言方法及参数
5.1.1、 assertEquals
用途:判断两个值是否相等。
参数:
expected:期望值actual:实际值message(可选):失败时显示的消息
用法:
assertEquals(expected, actual);
assertEquals(message, expected, actual);
示例:
assertEquals(5, add(2, 3));
assertEquals("加法结果不正确", 5, add(2, 3));
5.1.2、 assertNotEquals
用途:判断两个值是否不相等。
assertNotEquals(expected, actual);
assertNotEquals(message, expected, actual);
5.1.3、 assertTrue / assertFalse
用途:判断条件是否为 true 或 false。
assertTrue(condition);
assertTrue(message, condition);
assertFalse(condition);
assertFalse(message, condition);
示例:
assertTrue(isValid());
assertFalse("验证应失败", isValid());
5.1.4、 assertNull / assertNotNull
用途:判断对象是否为 null 或非 null。
assertNull(object);
assertNull(message, object);
assertNotNull(object);
assertNotNull(message, object);
5.1.5、 assertSame / assertNotSame
用途:判断两个对象引用是否是同一个对象。
assertSame(expected, actual);
assertNotSame(expected, actual);
5.1.6、 assertArrayEquals
用途:判断两个数组内容是否相等。
assertArrayEquals(expectedArray, actualArray);
assertArrayEquals(message, expectedArray, actualArray);
5.1.7、 assertThrows(JUnit 5)
用途:断言代码块抛出了指定异常。
assertThrows(Exception.class, () -> {
// 可能抛异常的代码
});
2. 代码示例
import static org.junit.Assert.*;
import org.junit.Test;
public class AssertDemo {
@Test
public void testAssert() {
assertEquals(4, 2 + 2);
assertNotEquals(5, 2 + 2);
assertTrue(3 > 2);
assertFalse(2 > 3);
assertNull(null);
assertNotNull(new Object());
assertSame("abc", "abc");
assertNotSame(new String("abc"), new String("abc"));
assertArrayEquals(new int[]{1,2,3}, new int[]{1,2,3});
}
}
JUnit 5 的异常断言示例:
import static org.junit.jupiter.api.Assertions.*;
@Test
void testException() {
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("error");
});
}
5.2、AssertJ 断言(更灵活、可读性更强)
AssertJ 是第三方断言库,支持链式断言,参数更多,语义更清晰。
常用用法:
import static org.assertj.core.api.Assertions.*;
@Test
void testAssertJ() {
assertThat(2 + 2).isEqualTo(4);
assertThat("abc").isNotNull().startsWith("a").endsWith("c");
assertThat(new int[]{1,2,3}).contains(2);
assertThatThrownBy(() -> { throw new RuntimeException("fail"); })
.isInstanceOf(RuntimeException.class)
.hasMessage("fail");
}
5.3、参数与用途总结
| 方法 | 主要参数 | 用途说明 |
|---|---|---|
| assertEquals | expected, actual, message | 判断相等 |
| assertNotEquals | expected, actual, message | 判断不相等 |
| assertTrue | condition, message | 判断为 true |
| assertFalse | condition, message | 判断为 false |
| assertNull | object, message | 判断为 null |
| assertNotNull | object, message | 判断非 null |
| assertSame | expected, actual, message | 判断引用相同 |
| assertNotSame | expected, actual, message | 判断引用不同 |
| assertArrayEquals | expected, actual, message | 判断数组内容相同 |
| assertThrows | exceptionClass, lambda | 判断抛出指定异常 |
5.4、使用建议
- 单元测试中,推荐用断言方法判断每个关键结果。
- JUnit 5 推荐用
assertThrows断言异常。 - 复杂对象、集合断言可用 AssertJ 提高可读性。
创作不易,点赞关注,持续更新!!!
3838

被折叠的 条评论
为什么被折叠?



