Mock测试概述:从"拿来吧你"到"我替你演"

先问个问题:你写过单元测试吗?写过,那一定遇到过这种尴尬——测试一个登录功能,结果网络不通,整个测试就挂了。更惨的是,数据库还没准备好,依赖的服务还在开发中……

这时候你就需要Mock了。说白了,Mock就是找一个"替身演员",让它替你真正的依赖对象上场表演。你让它返回什么它就返回什么,绝不出岔子。

什么是Mock?一个简单的类比

想象一下你在拍电影,主角是你要测试的类。但主角需要和一个"银行系统"演对手戏——可银行系统还没建好。怎么办?找个替身,长得差不多,台词按剧本说,该哭哭该笑笑。这个替身,就是Mock对象。

我记得我刚带团队时,有个同事写了200行的DAO测试,每次跑都要连真实MySQL。有一次DBA调整了表结构,全组测试炸成一片。我当时就说:你把数据库Mock掉啊!他才恍然大悟。嗯,这就是Mock的典型场景——让你只测自己的逻辑,不依赖外部环境。

对比项真实对象Mock对象
行为真实逻辑、可能抛出异常按设定返回、可预设行为
依赖需要数据库、网络、文件等零依赖
速度慢(IO开销)极快(纯内存)
确定性可能受环境干扰100%可重复

为什么要在Android测试中使用Mock?

你想想看,Android开发里到处是Context、SharedPreferences、Retrofit、Room。这些玩意你要在测试里真实调用——别说跑测试了,光初始化就要命。

举个例子:你要测一个LoginViewModel,它调用了AuthRepository.login()。如果不Mock,测试得启动Activity、等网络请求、处理回调……跑一次几秒钟。Mock之后呢?毫秒级搞定。而且可以轻松验证:到底有没有调用登录方法?传的参数对不对?回调走了哪种分支?

核心价值:

  • 解除外部依赖 —— 不联网、不连DB也能测
  • 隔离被测代码 —— 只测自己的逻辑,不测别人的bug
  • 验证交互行为 —— 是否调了方法、传了什么参数、调了几次
  • 制造边界场景 —— 网络异常、空数据、超时,全都能模拟
  • 加速测试反馈 —— 本地跑一次单元测试,5秒内出结果

我曾经在一个项目里遇到"诡异的Crash"——用户登录后随机闪退,但开发环境始终复现不了。后来通过Mock让AuthApi返回null ,马上定位到空指针。Mock就是这种"照妖镜",能把真实环境里不容易触发的bug给逼出来。

常见Mock框架对比:Mockito、MockK、EasyMock

业界主流就这三家。我个人习惯在Android项目里用MockK(Kotlin项目)或Mockito(Java项目)。EasyMock大概十年前比较火,现在基本被前两者取代了。我们来详细比比。

框架语言支持Kotlin友好度Mock静态/私有官方维护学习曲线
MockitoJava / Kotlin中等(需额外mockito-kotlin)需借助PowerMock活跃低
MockKKotlin优先优秀(原生支持协程、object、private)直接支持(通过relaxed mock)活跃中
EasyMockJava差需借助PowerMock低中

Mockito —— Java老大哥

如果你项目还是Java,或者Kotlin但想用最成熟的方案,选Mockito没问题。生态好,文档多,社区活跃。缺点是对Kotlin的一些特性(如object、companion object)支持比较别扭。

// 典型的Mockito用法(Java)
@RunWith(MockitoJUnitRunner.class)
public class LoginViewModelTest {
    @Mock
    AuthRepository authRepo;
    
    @Test
    public void testLoginSuccess() {
        when(authRepo.login("user", "pass")).thenReturn(new User("_123"));
        LoginViewModel vm = new LoginViewModel(authRepo);
        vm.login("user", "pass");
        verify(authRepo).login("user", "pass");
    }
}

小技巧:在Kotlin里用Mockito,建议引入mockito-kotlin库,能用every { ... }这种更自然的写法,少写when和verify的括号嵌套。

MockK —— Kotlin亲儿子

我个人最推荐在纯Kotlin项目里用MockK。它原生支持:

  • object单例Mock
  • companion object方法Mock
  • private方法Mock
  • 协程suspend函数Mock
  • 宽松模式(relaxed mock)——不设when也能返回默认值
// MockK 示例(Kotlin)
@Test
fun `test login success`() {
    val authRepo = mockk<AuthRepository>()
    every { authRepo.login("user", "pass") } returns User("_123")
    
    val vm = LoginViewModel(authRepo)
    vm.login("user", "pass")
    
    verify { authRepo.login("user", "pass") }
}

注意:MockK的verify默认是严格顺序的。如果你的代码调了多个方法但顺序不重要,记得用verifyAll或verifyOrder = false。我之前就踩过这个坑,测试明明正确但一直失败,查了半天才发现是调用顺序问题。

EasyMock —— 古典派

EasyMock在Java早期很流行,但这些年几乎被Mockito完全替代了。语法相对繁琐:需要expect/replay/verify三步曲。如果你不维护遗留项目,我建议直接跳过。

// EasyMock 示例(Java)
@RunWith(EasyMockRunner.class)
public class LoginViewModelTest {
    @TestSubject
    LoginViewModel vm = new LoginViewModel();
    @Mock
    AuthRepository authRepo;
    
    @Test
    public void testLogin() {
        expect(authRepo.login("user", "pass")).andReturn(new User(""));
        replay(authRepo);
        vm.login("user", "pass");
        verify(authRepo);
    }
}

怎么选?我的建议

  • 纯Kotlin新项目 → 直接上MockK,省心省力
  • Java项目或者混合项目,团队习惯Mockito → 继续用Mockito + mockito-kotlin
  • 遗留项目用了EasyMock → 别急着重构,先用着,新测试慢慢迁移

其实不用纠结太多。框架只是工具,关键是你理解Mock的理念:不依赖真实环境,只验证自己的代码逻辑。后续章节我会带你实战:在CI流水线里集成Mock,让每次代码提交后自动跑单元测试,发现问题秒级通知。

一句话总结:Mock就是让你的单元测试变得快、稳、准——快(不依赖IO)、稳(不受环境干扰)、准(精确验证调用)。

Mock测试核心框架与决策树 Mock 测试 Mockito MockK EasyMock 特性: • 生态成熟,文档多 • Java/Kotlin 均可 • 需额外支持Kotlin特性 特性: • 原生支持Kotlin • 支持协程/private/object • 宽松模式(relaxed) 特性: • 语法繁琐(三步曲) • 仅Java • 社区维护低 决策建议 纯Kotlin新项目 → MockK 团队习惯Java → Mockito 遗留EasyMock → 逐步迁移 核心原则 快:毫秒级运行 稳:100%重复性 准:精确验证交互