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静态/私有 | 官方维护 | 学习曲线 |
|---|---|---|---|---|---|
| Mockito | Java / Kotlin | 中等(需额外mockito-kotlin) | 需借助PowerMock | 活跃 | 低 |
| MockK | Kotlin优先 | 优秀(原生支持协程、object、private) | 直接支持(通过relaxed mock) | 活跃 | 中 |
| EasyMock | Java | 差 | 需借助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单例Mockcompanion object方法Mockprivate方法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)、稳(不受环境干扰)、准(精确验证调用)。