写单元测试时,被测类往往依赖数据库/外部服务——测试里不可能真连。Mockito 用"假对象(Mock)"替代真实依赖:不真干活,但能验证"被调用了、用了什么参数、返回什么"。这篇是 Mockito 快速入门(配合《JUnit 5 快速入门》使用)。
一、为什么要 Mock
java
public class UserService {
private final UserDao userDao; // 依赖数据库
public UserService(UserDao userDao) { this.userDao = userDao; }
public boolean createUser(String name) {
User exist = userDao.findByName(name);
if (exist != null) return false; // 重名拒绝
userDao.insert(new User(name));
return true;
}
}测试 createUser 时不能真连数据库——Mock 一个 UserDao,让它"假装"返回结果:
二、核心用法
java
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserDao userDao; // 1. Mock 依赖
@InjectMocks
private UserService userService; // 2. 自动注入 Mock(构造器注入)
@Test
void 重名用户_创建失败() {
when(userDao.findByName("BinMaker")).thenReturn(new User("BinMaker")); // 3. 预设返回值
boolean ok = userService.createUser("BinMaker");
assertFalse(ok);
verify(userDao, never()).insert(any()); // 4. 验证没调用 insert
}
@Test
void 新用户_创建成功() {
when(userDao.findByName("Alice")).thenReturn(null);
boolean ok = userService.createUser("Alice");
assertTrue(ok);
verify(userDao).insert(argThat(u -> u.getName().equals("Alice"))); // 验证参数
}
}三个核心动作:
| 动作 | 写法 | 作用 |
|---|---|---|
| 打桩(Stub) | when(x).thenReturn(y) | 预设 Mock 的返回值 |
| 验证(Verify) | verify(mock).method() | 断言方法被调用过、参数对不对 |
| 注入(Inject) | @InjectMocks | 把 Mock 塞进被测类 |
三、常用 API 速查
java
// ===== when / thenReturn(打桩) =====
when(userDao.findById(1L)).thenReturn(new User("BinMaker"));
when(userDao.findById(999L)).thenReturn(null);
when(userDao.findByName(anyString())).thenReturn(new User("默认"));
when(userDao.count()).thenReturn(5, 6); // 连续调用返回不同值
// ===== 异常场景 =====
when(userDao.insert(any())).thenThrow(new RuntimeException("DB 挂了"));
// ===== verify(验证) =====
verify(userDao).findByName("BinMaker"); // 确认调用过(默认 times(1))
verify(userDao, times(2)).findByName(anyString());
verify(userDao, never()).delete(any());
verify(userDao, atLeastOnce()).insert(any());
// ===== 参数匹配器 =====
any() / anyString() / anyLong() / eq(具体值) / argThat(自定义条件)
// ⚠️ 匹配器和具体值混用:具体值必须用 eq() 包
when(userDao.findByName(eq("a"), anyInt())).thenReturn(...);
// ===== 返回值的其他形式 =====
doThrow(new RuntimeException()).when(userDao).insert(any()); // void 方法抛异常用 doThrow
when(userDao.findByName(any())).thenAnswer(inv -> new User("动态")); // 动态答案四、Mock vs Spy
| Mock | Spy | |
|---|---|---|
| 行为 | 全部假,不调真实方法 | 默认调真实方法,可选择性打桩 |
| 适用 | 外部依赖(DB/HTTP) | 被测对象本身,想部分造假 |
| 声明 | @Mock | @Spy |
java
@Spy
private List<String> list = new ArrayList<>();
@Test
void spy_默认走真实方法() {
list.add("a");
verify(list).add("a"); // 真实调用
doReturn(100).when(list).size(); // 只有 size() 被造假
assertEquals(100, list.size());
}五、常见坑
- Mock 了 final 类/方法:默认 Mockito 不能 mock final——需要
mockito-inline(加依赖自动启用) - 静态方法(如
LocalDate.now()):默认不能 mock——mockito-inline的mockStatic():javatry (MockedStatic<LocalDate> mocked = mockStatic(LocalDate.class)) { when(LocalDate.now()).thenReturn(LocalDate.of(2026, 8, 26)); // ... 测试逻辑 } - verify 不写:只打桩不验证,Mock 的价值少了一半——打桩控返回、verify 控调用
- @InjectMocks 注入失败:构造器/字段名对不上——优先用构造器注入,别用字段注入
- Mock 和真实混用:能 Mock 的都 Mock,但别过度(Mock 太多说明设计耦合重)
六、Spring Boot 里怎么用
java
@WebMvcTest(controller = UserController.class) // 只加载 Controller 层
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean // Mock 掉 Service(Spring 容器里替换)
private UserService userService;
@Test
void 查用户_返回200() throws Exception {
when(userService.getById(1L)).thenReturn(new User("BinMaker"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("BinMaker"));
}
}Spring Boot 集成测试场景(不起真实容器)见《集成测试快速入门》。
小结
- Mockito = 测试里替代真实依赖的假对象:打桩(when/thenReturn)+ 验证(verify)+ 注入(@InjectMocks)
- Mock 全假、Spy 半真(默认真实、可选择性造假)
- 静态/final 方法需要
mockito-inline - 打桩控返回、verify 控调用,两者配合才是完整单测
- 依赖注入用构造器(配合 @InjectMocks 才稳)
