背景

很多公司都要求开发人员写单元测试,且通过公司的代码扫描,符合测试规则才让提交代码,目前使用最多的就是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));
}

输出结果:

1
10

而不会打印 “执行了真实方法!”,因为这次被 stub 覆盖了。如果你去掉那行 when(…),则会输出:

1
2
执行了真实方法!
5

(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(...):定义调用行为。
  • 默认所有未定义行为都会返回 null0false 等默认值。

第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) 测试隔离

总结

  1. 优先使用 @ExtendWith(MockitoExtension.class),不要手动 openMocks()
  2. @InjectMocks 自动注入依赖
  3. 验证策略与最佳实践建议
场景 建议
Mock 范围 只 Mock 外部依赖,不 Mock 自己写的逻辑
测试结构 Given(准备)→ When(执行)→ Then(验证)
命名规范 should_行为_when_条件
避免 Mock 框架内部类、Mock 太多层级、Mock 配置类
推荐 BDD 风格、参数捕获、断言库结合(AssertJ、Hamcrest)
工程实战 把 Mock 放在 src/test/java 层,不入主工程