Mock框架选择与配置:Mockk与Mockito在Compose中的使用、依赖配置
好,我们直接进入本章最核心的实操环节。很多人一上来就问:Compose 里到底用 Mockk 还是 Mockito?我个人的回答是——都可以,但要看场景。这两个框架在 Android 圈子里都算老熟人了,但到了 Compose 这个新世界,它们的一些特性差异会被放大。
两个框架的核心差异
先上一张对比表,帮你快速建立认知框架:
| 特性 | Mockk | Mockito |
|---|---|---|
| Kotlin 原生支持 | ✅ 一等公民,支持协程、扩展函数 | ⚠️ 需要额外配置 mockito-kotlin |
| Compose 测试支持 | ✅ 有专门的 compose-mockk 扩展库 | ✅ 通过 mockito-android 支持 |
| 私有方法/静态方法 | ✅ 内置支持(@RelaxedMockK) | ❌ 需 PowerMock(不推荐) |
| 协程 Mock | ✅ coEvery / coVerify 原生 | ⚠️ 需手动处理 suspend 函数 |
| 学习曲线 | 中等(语法糖较多) | 较低(传统的 when/thenReturn) |
| 社区活跃度 | 📈 近年上升明显 | 📊 老牌稳定,生态完整 |
你可能会问:为什么 Mockk 在 Compose 里越来越流行?说白了,Kotlin 的很多语法糖在 Mockito 里模拟起来很痛苦。比如 sealed class、operator 重载、协程 suspend……我在一个 Compose 项目里用 Mockito 去 mock 一个 ViewModel 的 StateFlow,硬是配了半天才跑通。换成 Mockk 之后,一行 coEvery { viewModel.uiState } returns MutableStateFlow(...) 就搞定了。
依赖配置:Gradle 里该加什么
不管选哪个,依赖都得配对。以我最常用的 Mockk 为例,分享一份完整的 Gradle 配置:
// 项目级 build.gradle.kts (root)
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") } // 有些扩展库需要
}
}
// 模块级 build.gradle.kts (app)
dependencies {
// 核心库
testImplementation("io.mockk:mockk:1.13.10")
// 如果需要在 Compose 测试中使用(androidTest 或 debugImplementation)
debugImplementation("io.mockk:mockk-android:1.13.10")
// 协程支持(默认会随核心库带入,但建议显式声明)
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
// Compose 测试框架
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0")
debugImplementation("androidx.compose.ui:ui-test-junit4:1.6.0")
// 如果你还是想用 Mockito(比如和老项目混用)
// testImplementation("org.mockito:mockito-core:5.8.0")
// testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
// testImplementation("org.mockito:mockito-inline:5.2.0")
}
小技巧: 如果你用的是 Android Instrumented Test(androidTest),记得用
debugImplementation 而不是 testImplementation。我曾经因为写错了 scope,导致测试跑起来报 ClassNotFoundException,排查了半天……后来才发现是 scope 问题。
配置中的几个坑
配置看起来简单,但有几个细节很容易翻车。我一个个说:
- Mockito + Kotlin 协程: 必须引入
mockito-kotlin扩展,并且需要手动处理 suspend 函数的返回值。我曾经用一个thenReturn去 mock 一个 suspend 函数,结果测试永远超时。后来换成thenAnswer { ... }才解决。 - Mockk 的 @RelaxedMockK 注解: 如果你只是需要一个“哑实现”来占位,这个注解能让所有函数返回默认值(null、空集合等)。但注意——它不会 mock 协程函数,需要配合
@MockK(relaxed = true)使用。 - Compose 测试中的 Mock 注入: Compose UI 组件通常通过 ViewModel 或 use case 获取数据。我建议用
ComposeTestRule配合AndroidComposeTestRule,然后把 Mock 对象注入到ViewModelProvider.Factory里。
注意: 不要在
testImplementation 里引入 mockk-android——它会依赖 Android 环境,而纯单元测试运行在 JVM 上,报错 ClassNotFoundException: android.os.Looper。如果你的 Compose 测试跑在 instrumented test 里,才用 debugImplementation。
框架选择决策流程
我平时接一个 Compose 项目时,会按下面的流程快速决定用哪个框架。这张图能帮你眼熟一下整个配置路线:
核心思路: 如果项目是纯 Kotlin(包括 Compose),我无脑推荐 Mockk——它跟 Kotlin 的语法贴合度最高,协程支持天然好。如果团队里还有大量 Java 模块,或者你在维持一个老项目,那 Mockito 依然是稳妥之选。两个框架甚至可以共存,只要注意 class 隔离。
实战配置示例:一个完整的 Compose ViewModel 测试
光说不练假把式。我直接给你一段我项目里常用的测试代码,配置好依赖后就能跑:
// 在 androidTest 或 test 目录下
class LoginViewModelTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
// Mockk 方式
@MockK
lateinit var loginUseCase: LoginUseCase
private lateinit var viewModel: LoginViewModel
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
// 通过 Factory 注入 Mock
viewModel = LoginViewModel(
loginUseCase = loginUseCase
)
// 将 ViewModel 设置到 Compose 测试环境
composeTestRule.setContent {
LoginScreen(viewModel = viewModel)
}
}
@Test
fun testLoginSuccess() = runTest {
// 使用 coEvery 模拟协程
coEvery { loginUseCase.execute(any()) } returns Result.success(true)
// 执行 UI 操作(假设点击登录按钮)
composeTestRule.onNodeWithTag("login_button").performClick()
// 验证
coVerify { loginUseCase.execute(any()) }
}
}
踩过的坑: 记得在
setUp() 里调用 MockKAnnotations.init(this)——这个我至少忘了两次,然后一直报 lateinit property has not been initialized。另外,如果你的 Compose 组件内部依赖了 Context,需要确保测试环境有正确的 Context 提供,否则会空指针。
最后说两句配置顺序
我把依赖配置的步骤整理成了一个清单,方便你对照检查:
- 确定项目定位:纯 Kotlin / 混合项目 / 已有 Mockito 大量代码。
- 添加根仓库:将
mavenCentral()、jitpack.io(可选)加入 repositories。 - 添加模块依赖:根据测试类型(test / androidTest)添加对应的 mock 框架依赖。
- 添加协程测试依赖:
kotlinx-coroutines-test必须,不然runTest用不了。 - Compose 测试基础设施:
ui-test-manifest和ui-test-junit4缺一不可。 - 注入 Mock 对象:在
@Before中初始化 Mock,并通过 ViewModelFactory 传入 Compose 预览。
嗯,到这步,你的 Compose Mock 测试环境就算搭建完毕了。可以放心写测试了。