背景
很多公司都要求开发人员写单元测试,且通过公司的代码扫描,符合测试规则才让提交代码,目前使用最多的就是Mockito了。Mockito 是一个用于测试的框架,用于模拟对象。 它允许开发人员创建和配置模拟对象,以便在单元测试中隔离被测试的类,从而专注于测试其行为和逻辑,而不受外部依赖的影响。
第1章:Mockito 入门与基本概念
1.1 什么是 Mock?
在单元测试中,我们通常只想测试一个类的业务逻辑,而不想让外部依赖(数据库、网络、MQ)影响测试。
这时就要用 Mock(模拟对象) 来代替真实依赖。
Mockito 的本质,通过动态代理创建“伪对象”,拦截方法调用并返回预设结果。
举例:
我们测试 UserService,但它依赖 UserRepository(访问数据库)。
我们只 Mock 掉 UserRepository,而测试 UserService 的逻辑是否正确。
1.2 Mockito 核心 API
核心注解
| 注解 |
作用 |
@Mock |
创建一个 Mock 对象(不会执行真实逻辑) |
@Spy |
创建一个部分 Mock 的真实对象(会执行真实逻辑,除非被 stub 覆盖) |
@InjectMocks |
创建被测试的真实对象,并自动注入标注了 @Mock 或 @Spy 的依赖 |
@Captor |
声明一个参数捕获器,用于获取被调用方法的入参 |
@ExtendWith(MockitoExtension.class) |
启用 Mockito 的 JUnit5 扩展,使注解生效 |
@MockBean (Spring Boot) |
在 Spring 容器中创建一个 Mock Bean(替换原始 Bean) |
@SpyBean (Spring Boot) |
在 Spring 容器中创建一个 Spy Bean(保留真实逻辑,可局部 Mock) |
@Spy —— “部分 Mock” 是什么意思?
部分 Mock:表示对象是真实的,不是假的。Mockito 不会拦截全部方法调用。
会执行真实逻辑:调用这个对象的方法时,默认执行它原本的实现。
除非被 stub 覆盖:如果你用 when(…).thenReturn(…) 明确指定某个方法的返回结果,这个方法就不再执行真实逻辑,而使用你定义的结果。
举个例子:
1
2
3
4
5
6
7
8
|
class Calculator {
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
|
情况 1:普通 Mock(完全假)
1
2
3
4
5
6
7
8
9
|
@Mock
private Calculator calculator;
@Test
void testMock() {
when(calculator.add(2, 3)).thenReturn(10);
System.out.println(calculator.add(2, 3)); // 输出 10
System.out.println(calculator.multiply(2, 3)); // 输出 0(因为没 stub,默认值)
}
|
Mock:所有方法都“假”,你得告诉它返回什么,否则全是默认值(null、0、false)
情况 2:Spy(部分 Mock)
1
2
3
4
5
6
7
8
9
|
@Spy
private Calculator calculator = new Calculator();
@Test
void testSpy() {
when(calculator.add(2, 3)).thenReturn(10); // 覆盖 add 方法
System.out.println(calculator.add(2, 3)); // 输出 10(被 stub 覆盖)
System.out.println(calculator.multiply(2, 3)); // 输出 6(真实执行)
}
|
Spy:默认执行真实方法,但你可以选择性“假装”部分方法。这就是“部分 Mock”。
@InjectMocks —— 自动注入的原理是什么?
假设你要测试一个 UserService,而它依赖了一个 UserRepository:
1
2
3
4
5
6
7
8
9
10
11
|
class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public String getUserName(Long id) {
return repository.findById(id).getName();
}
}
|
如果你用 @InjectMocks,Mockito 会帮你自动把这些 Mock 注入进来:
1
2
3
4
5
|
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService; // ← Mockito 帮你自动注入 userRepository
|
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class UserRepository {
public String findNameById(Long id) {
return "RealName"; // 真实逻辑
}
}
class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { this.repo = repo; }
public String getUserName(Long id) {
return repo.findNameById(id);
}
}
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository; // 依赖:Mock 假的
@InjectMocks
private UserService userService; // 被测试的真实对象,自动注入 userRepository
@Test
void testGetUserName() {
when(userRepository.findNameById(1L)).thenReturn("MockTom");
assertEquals("MockTom", userService.getUserName(1L));
}
}
|
创建了一个假的 userRepository,这里的 userService 是一个真实对象,但它里面的 userRepository 字段是 Mock 出来的,Mockito 自动帮你“塞”进去的。
创建与行为定义
| 方法 |
作用 |
mock(Class<T> classToMock) |
手动创建一个 Mock 对象 |
spy(T realObject) |
基于真实对象创建 Spy(半 Mock) |
when(mock.method()).thenReturn(value) |
定义 Mock 行为(返回固定值) |
when(mock.method()).thenThrow(exception) |
定义 Mock 抛出异常 |
doReturn(value).when(mock).method() |
Spy 模式下安全地定义行为(不会调用真实方法) |
doThrow(exception).when(mock).method() |
定义方法调用时抛出异常 |
doNothing().when(mock).method() |
忽略方法执行(不抛异常不执行逻辑) |
参数匹配(ArgumentMatchers)
| 匹配器 |
作用 |
any() / any(Class<T>) |
匹配任意对象 |
anyString() / anyLong() / anyInt() |
匹配任意类型值 |
eq(value) |
匹配等于指定值 |
isNull() / notNull() |
匹配空或非空值 |
argThat(predicate) |
使用 Lambda 自定义匹配逻辑 |
示例:
1
2
3
|
when(repo.findByName(anyString())).thenReturn(new User(1L, "Tom"));
when(repo.save(argThat(u -> u.getName().startsWith("T"))))
.thenReturn(new User(1L, "Tom"));
|
参数捕获(ArgumentCaptor)
| API |
作用 |
ArgumentCaptor.forClass(Class<T>) |
创建捕获器 |
@Captor 注解 |
自动注入捕获器 |
captor.capture() |
捕获传入参数 |
captor.getValue() / getAllValues() |
获取捕获值(单个或多个) |
示例:
1
2
3
4
|
@Captor
ArgumentCaptor<Long> idCaptor;
verify(repo).delete(idCaptor.capture());
assertEquals(5L, idCaptor.getValue());
|
部分 Mock(Spy)
| 方法 |
作用 |
spy(realObject) |
基于真实对象创建 Spy |
doReturn(value).when(spy).method() |
安全定义 Spy 行为(不触发真实逻辑) |
doThrow(...).when(spy).method() |
使 Spy 方法抛出异常 |
示例:
1
2
3
4
|
List<String> spyList = spy(new ArrayList<>());
doReturn(100).when(spyList).size();
spyList.add("A");
assertEquals(100, spyList.size());
|
静态方法 Mock(需要 mockito-inline 依赖)
| API |
作用 |
mockStatic(Class<T>) |
开启静态方法 Mock |
mock.when(() -> Class.method()).thenReturn(value) |
定义静态方法返回值 |
mock.verify(() -> Class.method()) |
验证静态方法是否调用 |
使用 try-with-resources 自动关闭作用域 |
避免静态 Mock 泄漏 |
示例:
1
2
3
4
|
try (MockedStatic<Math> mockMath = mockStatic(Math.class)) {
mockMath.when(Math::random).thenReturn(0.5);
assertEquals(0.5, Math.random());
}
|
异常与返回控制
| 方法 |
作用 |
thenThrow(Exception e) |
抛出异常 |
thenReturn(value) |
返回固定值 |
thenAnswer(invocation -> ...) |
根据调用参数动态返回 |
thenCallRealMethod() |
调用真实方法(适用于 Spy 或部分 Mock) |
示例:
1
2
|
when(repo.findById(anyLong()))
.thenAnswer(inv -> new User(inv.getArgument(0), "DynamicName"));
|
BDD 风格(行为驱动开发)
| API |
对应传统 API |
given(...) |
when(...) |
willReturn(...) |
thenReturn(...) |
willThrow(...) |
thenThrow(...) |
then(mock).should() |
verify(mock) |
then(mock).shouldHaveNoMoreInteractions() |
verifyNoMoreInteractions(mock) |
示例:
1
2
3
4
5
|
import static org.mockito.BDDMockito.*;
given(repo.findById(1L)).willReturn(Optional.of(new User(1L, "Tom")));
String name = service.getUserName(1L);
then(repo).should(times(1)).findById(1L);
|
Spring Boot 集成专用注解
| 注解 |
作用 |
@SpringBootTest |
启动完整 Spring 上下文 |
@MockBean |
替换容器中的 Bean 为 Mock |
@SpyBean |
替换 Bean 为 Spy,保留部分真实逻辑 |
@AutoConfigureMockMvc |
结合 MockMvc 测试控制器 |
@WebMvcTest |
只加载 Web 层 Bean(适合 Controller 测试) |
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@SpringBootTest
class OrderServiceTest {
@MockBean
private PaymentClient paymentClient;
@Autowired
private OrderService orderService;
@Test
void testPayment() {
when(paymentClient.pay(any())).thenReturn(true);
assertTrue(orderService.createOrder("A"));
}
}
|
1.3 什么是 verify?
verify() 是 Mockito 的“行为验证”核心函数,也是和 when(…).thenReturn(…)(行为设定)相对的另一半。核心思想 verify 用来验证行为是否发生,verify(mock) 会返回一个 代理对象,这个代理对象的每个方法调用(如 .add())都会被 Mockito 拦截,Mockito 会记录 “哪个方法被调用了、参数是什么,然后根据你传入的模式如 times() 等模式进行比对,所以 verify(mock).add(“xx”) 是一种非常自然的 DSL(领域特定语言)写法。在 Mockito 中,测试分为两大类:
| 测试类型 |
目的 |
| Stub(打桩) |
告诉 Mock “应该怎么返回” |
| Verify(验证) |
检查 Mock “是否被调用过” |
(1)verify() 的语法格式
基本格式:
1
|
verify(mockObject).methodName(arguments);
|
意思是:检查 mockObject 这个假对象的某个方法,是否真的在测试执行中被调用过。
举个例子:
1
2
3
4
5
6
7
8
|
List<String> mockList = mock(List.class);
// 调用代码
mockList.add("one");
mockList.add("two");
// 验证
verify(mockList).add("one");
|
意思是:验证 mockList.add(“one”) 这个调用在执行过程中 至少被调用过一次。
(2)加上次数校验 —— times(n)
如果你想验证这个方法被调用的次数,可以这么写:
1
|
verify(mockList, times(2)).add("one");
|
意思是:验证 mockList 的 add(“one”) 方法被调用了 2 次。
示例1:
1
2
3
4
5
6
7
8
|
List<String> mockList = mock(List.class);
mockList.add("hello");
mockList.add("world");
mockList.add("hello");
verify(mockList, times(2)).add("hello"); // 成功
verify(mockList, times(1)).add("world"); // 成功
verify(mockList, never()).clear(); // 成功,因为没被调用
|
示例2:
1
2
3
4
5
6
7
8
9
|
@Test
void testVerify() {
List<String> mockList = mock(List.class);
mockList.add("apple");
mockList.add("banana");
verify(mockList, times(2)).add(anyString()); // 成功 因为 mockList.add(...) 被调用了 2 次,并且参数都是字符串
}
|
(3)verify() 的完整语法结构
1
|
verify(mock, verificationMode).method(arguments);
|
| 参数 |
说明 |
mock |
要验证的 Mock 对象 |
verificationMode |
调用验证模式,如 times(n)、never()、atLeastOnce() 等 |
method(arguments) |
要验证的方法及其参数 |
常见的 verificationMode 模式
| 验证模式 |
说明 |
times(n) |
验证被调用了 n 次 |
never() |
验证从未被调用 |
atLeastOnce() |
至少被调用 1 次 |
atLeast(n) |
至少被调用 n 次 |
atMost(n) |
最多被调用 n 次 |
only() |
验证只调用了这一个方法,没有别的方法被调用 |
timeout(ms) |
(异步测试)验证在指定时间内被调用 |
(4)行为验证
| 方法 |
作用 |
verify(mock).method() |
验证方法被调用过至少一次 |
verify(mock, times(n)).method(args) |
验证方法被调用 n 次 |
verify(mock, never()).method(args) |
验证方法从未被调用 |
verify(mock, atLeast(n)).method(args) |
验证至少被调用 n 次 |
verify(mock, atMost(n)).method(args) |
验证最多被调用 n 次 |
verify(mock, only()).method(args) |
验证只调用了这一个方法 |
verify(mock, timeout(ms)).method(args) |
在一定时间内调用成功(常用于异步) |
verifyNoInteractions(mock...) |
验证 Mock 没有任何交互 |
verifyNoMoreInteractions(mock...) |
验证除了已验证的之外,没有多余调用 |
例子:
1
2
3
|
verify(repo, times(1)).save(any());
verify(repo, never()).delete(any());
verify(repo, atLeast(2)).findById(anyLong());
|
1.4 什么是 Stub?
在 Mockito 语境中,Stub(桩) 是一种预先定义的假行为,告诉 Mock 对象:“当我调用这个方法时,你要返回什么结果”。换句话说,Stub 就是 你人为指定 Mock 对象在特定输入下的表现。
举例:最基础的 stub
1
2
|
when(userRepository.findById(1L))
.thenReturn(new User(1L, "Tom"));
|
意思是:当调用 userRepository.findById(1L) 时,不要执行真实逻辑,直接返回 new User(1L, “Tom”)。这里的 when(…).thenReturn(…) 就是一次 stubbing 操作。
(1)stub 是干什么用的?
stub 的作用是,替换掉原本可能很复杂、不可控、耗时的真实逻辑。
举例来说,你在写一个 UserService:
1
2
3
4
5
6
7
8
|
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { this.repo = repo; }
public String getUserName(Long id) {
return repo.findById(id).getName();
}
}
|
如果 UserRepository 要访问数据库,你在单测时并不想真的连数据库。
那就这样:
1
2
3
4
5
6
7
8
9
10
11
|
@Mock
private UserRepository repo;
@InjectMocks
private UserService service;
@Test
void testGetUserName() {
when(repo.findById(1L)).thenReturn(new User(1L, "MockTom"));
assertEquals("MockTom", service.getUserName(1L));
}
|
这里 when(…).thenReturn(…) 的那一行,就是 stubbing —— 告诉 Mockito:“这次调用就别查数据库了,我自己定义返回值。”
(2)那 “除非被 stub 覆盖” 是什么意思?
现在我们回看 @Spy:“创建一个部分 Mock 的真实对象(会执行真实逻辑,除非被 stub 覆盖)”。也就是,默认情况下,Spy 会调用真实方法,但是如果你对这个方法做了 stub(when(…).thenReturn(…)),那么 Mockito 会优先执行 stub 的结果,不再调用真实方法。
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Calculator {
public int add(int a, int b) {
System.out.println("执行了真实方法!");
return a + b;
}
}
@Spy
private Calculator calculator = new Calculator();
@Test
void testSpyStub() {
// 对 add 方法做 stub
when(calculator.add(2, 3)).thenReturn(10);
System.out.println(calculator.add(2, 3));
}
|
输出结果:
而不会打印 “执行了真实方法!”,因为这次被 stub 覆盖了。如果你去掉那行 when(…),则会输出:
(3)特别说明:Spy 的安全 stub 写法
Spy 是真实对象,如果你直接用 when(spy.method()),Mockito 会真的执行一次该方法(可能产生副作用)。
所以推荐安全写法:
doReturn(10).when(calculator).add(2, 3);
这不会执行真实方法,只是告诉 Mockito 以后碰到这行调用,返回 10。
(4)常见的 stub 写法
| 写法 |
说明 |
when(mock.method()).thenReturn(value) |
调用方法时返回固定值 |
when(mock.method()).thenThrow(Exception.class) |
调用方法时抛出异常 |
doReturn(value).when(mock).method() |
针对 @Spy 更安全的 stub 方式(避免真实调用) |
doThrow(Exception.class).when(mock).method() |
调用时抛异常 |
doNothing().when(mock).method() |
调用时什么都不做(常用于 void 方法) |
(5)总结
| 名称 |
含义 |
示例 |
特点 |
| Mock |
一个完全虚假的对象,用来代替真实依赖 |
@Mock UserRepository repo; |
默认所有方法返回空值 |
| Spy |
一个部分 Mock 的真实对象(默认执行真实逻辑) |
@Spy Calculator calc; |
可局部替换逻辑 |
| Stub |
对 Mock/Spy 对象定义的假行为 |
when(repo.findById(1L)).thenReturn(user) |
控制方法返回或抛异常 |
| InjectMocks |
创建被测试的真实对象,并注入上面定义的 Mock/Spy |
@InjectMocks UserService service; |
自动组装依赖 |
1.5 环境准备
Maven 依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- 如果需要 mock 静态方法 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 集成 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
|
1.6 Hello Mockito
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class HelloMockitoTest {
@Test
void testSimpleMock() {
List<String> mockList = Mockito.mock(List.class);
Mockito.when(mockList.get(0)).thenReturn("Hello Mockito!");
assertEquals("Hello Mockito!", mockList.get(0));
assertNull(mockList.get(1)); // 未定义返回null
}
}
|
解析:
Mockito.mock(List.class):创建一个模拟 List 对象。
when(...).thenReturn(...):定义调用行为。
- 默认所有未定义行为都会返回
null、0、false 等默认值。
第2章:基础 Mock 与行为验证
2.1 基本结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService; // 自动注入 mock 对象
@Test
void testGetUser() {
when(userRepository.findById(1L)).thenReturn(new User(1L, "Tom"));
User result = userService.getUser(1L);
assertEquals("Tom", result.getName());
verify(userRepository, times(1)).findById(1L); // 验证调用次数
}
}
|
说明:
@Mock:创建一个模拟对象;
@InjectMocks:创建一个真实对象,并自动注入所有标注了 @Mock 的依赖;
verify():验证方法是否被调用。
第3章:参数匹配与返回值控制
3.1 参数匹配器(Argument Matchers)
1
2
3
4
5
6
7
8
9
|
@Test
void testWithMatchers() {
List<String> mockList = mock(List.class);
when(mockList.get(anyInt())).thenReturn("Hello");
assertEquals("Hello", mockList.get(999));
verify(mockList).get(anyInt());
}
|
常用匹配器:
any(), anyInt(), anyString()
eq(value) — 精确匹配
argThat(predicate) — 自定义匹配条件
3.2 多次调用不同返回值
1
2
3
4
5
6
7
8
9
|
@Test
void testMultipleReturns() {
List<String> mockList = mock(List.class);
when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3);
assertEquals(1, mockList.size());
assertEquals(2, mockList.size());
assertEquals(3, mockList.size());
}
|
第4章:@InjectMocks 与依赖注入
1
2
3
4
5
6
7
8
9
10
|
class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUser(Long id) {
return userRepository.findById(id);
}
}
|
测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@ExtendWith(MockitoExtension.class)
class InjectMockTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService; // userRepository 自动注入
@Test
void testInject() {
when(userRepository.findById(1L)).thenReturn(new User(1L, "Alice"));
assertEquals("Alice", userService.findUser(1L).getName());
}
}
|
第5章:Spy(部分真实对象)
1
2
3
4
5
6
7
8
9
10
11
12
|
@Test
void testSpyExample() {
List<String> spyList = spy(new ArrayList<>());
spyList.add("A");
spyList.add("B");
when(spyList.size()).thenReturn(100);
assertEquals(100, spyList.size());
assertEquals("A", spyList.get(0));
}
|
区别:
- Mock:完全虚拟,不执行真实逻辑;
- Spy:部分 Mock,默认调用真实方法,但可覆盖指定方法。
第6章:异常、返回值与调用次数验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Test
void testExceptionThrowing() {
List<String> mockList = mock(List.class);
when(mockList.get(0)).thenThrow(new RuntimeException("Boom!"));
assertThrows(RuntimeException.class, () -> mockList.get(0));
}
@Test
void testVerifyTimes() {
List<String> mockList = mock(List.class);
mockList.add("A");
mockList.add("B");
verify(mockList, times(2)).add(anyString());
verify(mockList, never()).clear();
}
|
第7章:Mock 静态方法(mockito-inline)
1
2
3
4
5
6
7
8
9
|
@Test
void testMockStatic() {
try (MockedStatic<UUID> mocked = mockStatic(UUID.class)) {
mocked.when(UUID::randomUUID)
.thenReturn(UUID.fromString("00000000-0000-0000-0000-000000000000"));
assertEquals("00000000-0000-0000-0000-000000000000", UUID.randomUUID().toString());
}
}
|
注意:
- 需要引入
mockito-inline;
- 静态 Mock 必须放在 try-with-resources 中;
- 适用于测试工具类。
第8章:Spring Boot 集成实战
在 Spring Boot 测试中,使用 @MockBean 和 @SpyBean 代替 Mockito 的 @Mock / @Spy。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@SpringBootTest
class UserServiceSpringTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void testFindUser() {
when(userRepository.findById(1L)).thenReturn(new User(1L, "Tom"));
assertEquals("Tom", userService.findUser(1L).getName());
}
}
|
第9章:Mockito 实战
1. 目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mockito-demo/
├─ src/
│ ├─ main/
│ │ └─ java/com/example/mockito/
│ │ ├─ model/User.java
│ │ ├─ repository/UserRepository.java
│ │ ├─ service/UserService.java
│ │ └─ controller/UserController.java
│ └─ test/
│ └─ java/com/example/mockito/
│ ├─ service/UserServiceTest.java
│ ├─ service/UserServiceAdvancedTest.java
│ └─ controller/UserControllerTest.java
└─ pom.xml
|
2. pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mockito-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<spring.boot.version>3.3.2</spring.boot.version>
<mockito.version>5.11.0</mockito.version>
<junit.jupiter.version>5.10.2</junit.jupiter.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- 测试相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito 核心 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- 支持静态方法 Mock -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
|
3. 业务代码部分
(1)model/User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package com.example.mockito.model;
public class User {
private Long id;
private String name;
public User() {}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() { return id; }
public String getName() { return name; }
}
|
(2)repository/UserRepository.java
1
2
3
4
5
6
7
8
9
10
|
package com.example.mockito.repository;
import com.example.mockito.model.User;
import java.util.Optional;
public interface UserRepository {
Optional<User> findById(Long id);
void save(User user);
void delete(Long id);
}
|
(3)service/UserService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.example.mockito.service;
import com.example.mockito.model.User;
import com.example.mockito.repository.UserRepository;
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserName(Long id) {
return userRepository.findById(id)
.map(User::getName)
.orElse("Unknown");
}
public void registerUser(User user) {
if (user.getName() == null || user.getName().isBlank()) {
throw new IllegalArgumentException("Invalid user");
}
userRepository.save(user);
}
public boolean deleteUser(Long id) {
userRepository.delete(id);
return true;
}
}
|
4. 测试代码部分
(1)基础 Mock 测试 UserServiceTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.example.mockito.service;
import com.example.mockito.model.User;
import com.example.mockito.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void should_return_user_name_when_user_exists() {
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User(1L, "Tom")));
String name = userService.getUserName(1L);
assertEquals("Tom", name);
verify(userRepository, times(1)).findById(1L);
}
@Test
void should_throw_exception_when_register_invalid_user() {
User user = new User(1L, "");
assertThrows(IllegalArgumentException.class, () -> userService.registerUser(user));
}
}
|
(2)高级 Mock 测试 UserServiceAdvancedTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.example.mockito.service;
import com.example.mockito.model.User;
import com.example.mockito.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class UserServiceAdvancedTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Captor
private ArgumentCaptor<Long> idCaptor; // ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class);
@Test
void should_capture_argument_when_delete_called() {
userService.deleteUser(10L); // ① 调用被测方法
verify(userRepository).delete(idCaptor.capture()); // ② 验证并捕获参数
assertEquals(10L, idCaptor.getValue()); // ③ 断言参数值
}
@Test
void should_mock_static_method() {
try (MockedStatic<Math> mockMath = mockStatic(Math.class)) {
mockMath.when(Math::random).thenReturn(0.8);
assertEquals(0.8, Math.random());
}
}
@Test
void should_throw_runtime_exception_when_find_failed() {
when(userRepository.findById(anyLong()))
.thenThrow(new RuntimeException("DB error"));
assertThrows(RuntimeException.class, () -> userService.getUserName(1L));
}
}
|
(3)Spring Boot 实战测试 UserControllerTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.example.mockito.controller;
import com.example.mockito.model.User;
import com.example.mockito.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserControllerTest {
@Autowired
private UserController userController;
@MockBean
private UserService userService;
@Test
void should_return_name_from_mocked_service() {
when(userService.getUserName(1L)).thenReturn("MockTom");
assertEquals("MockTom", userController.getUser(1L));
}
}
|
对应的 UserController.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.example.mockito.controller;
import com.example.mockito.service.UserService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
return userService.getUserName(id);
}
}
|
第10章:Mockito 高级技巧与最佳实践
| 技巧 |
示例 |
用途 |
| 捕获参数 |
ArgumentCaptor |
断言传递参数 |
| 超时验证 |
verify(mock, timeout(100)).method() |
异步测试 |
| BDD 风格 |
given(...).willReturn(...) |
行为驱动测试语义 |
| Mock 深层对象 |
RETURNS_DEEP_STUBS |
快速 Mock 多层调用 |
| 清理与重置 |
reset(mock) |
测试隔离 |
总结
- 优先使用
@ExtendWith(MockitoExtension.class),不要手动 openMocks()
- 用
@InjectMocks 自动注入依赖
- 验证策略与最佳实践建议
| 场景 |
建议 |
| Mock 范围 |
只 Mock 外部依赖,不 Mock 自己写的逻辑 |
| 测试结构 |
Given(准备)→ When(执行)→ Then(验证) |
| 命名规范 |
should_行为_when_条件 |
| 避免 |
Mock 框架内部类、Mock 太多层级、Mock 配置类 |
| 推荐 |
BDD 风格、参数捕获、断言库结合(AssertJ、Hamcrest) |
| 工程实战 |
把 Mock 放在 src/test/java 层,不入主工程 |