Skip to content

写单元测试时,被测类往往依赖数据库/外部服务——测试里不可能真连。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

MockSpy
行为全部假,不调真实方法默认调真实方法,可选择性打桩
适用外部依赖(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());
}

五、常见坑

  1. Mock 了 final 类/方法:默认 Mockito 不能 mock final——需要 mockito-inline(加依赖自动启用)
  2. 静态方法(如 LocalDate.now()):默认不能 mock——mockito-inlinemockStatic()
    java
    try (MockedStatic<LocalDate> mocked = mockStatic(LocalDate.class)) {
        when(LocalDate.now()).thenReturn(LocalDate.of(2026, 8, 26));
        // ... 测试逻辑
    }
  3. verify 不写:只打桩不验证,Mock 的价值少了一半——打桩控返回、verify 控调用
  4. @InjectMocks 注入失败:构造器/字段名对不上——优先用构造器注入,别用字段注入
  5. 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 才稳)