在 Android 开发中,单元测试是保障代码质量的核心手段。但面对复杂的依赖关系和 Kotlin 语言特性,传统 Mock 框架常显得力不从心。本文将带你深入 MockK —— 一款专为 Kotlin 设计的 Mock 框架,通过 20+ 真实场景代码示例,助你彻底掌握 MockK 的精髓。
coEvery
/coVerify
)object
单例类框架 | 启动时间 | 内存占用 | Kotlin 适配度 |
---|---|---|---|
MockK | 120ms | 45MB | ★★★★★ |
Mockito | 200ms | 60MB | ★★★☆☆ |
PowerMock | 350ms | 85MB | ★★☆☆☆ |
// module/build.gradle.kts
dependencies {
testImplementation("io.mockk:mockk:1.13.8")
testImplementation("io.mockk:mockk-agent-jvm:1.13.8") // 解决 JDK 17+ 兼容问题
androidTestImplementation("io.mockk:mockk-android:1.13.8") // 仪器化测试
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") // 协程支持
}
interface AuthService {
fun login(username: String, password: String): Boolean
}
@Test
fun `login should return true when credentials valid`() {
val authMock = mockk<AuthService>()
// Stubbing 配置
every {
authMock.login(
username = eq("admin"), // 精确匹配
password = any() // 任意密码
)
} returns true
assertTrue(authMock.login("admin", "123456"))
verify(exactly = 1) { authMock.login(any(), any()) }
}
class PaymentProcessor {
fun process(amount: Double) {
if (amount <= 0) throw IllegalArgumentException()
// 真实支付逻辑
}
}
@Test
fun `process should throw when amount invalid`() {
val processor = mockk<PaymentProcessor>()
every { processor.process(any()) } throws IllegalArgumentException("Invalid amount")
assertThrows<IllegalArgumentException> {
processor.process(-100.0)
}
}
class AnalyticsTracker {
fun trackEvent(event: String, params: Map<String, Any>) {
// 上报事件
}
}
@Test
fun `trackEvent should contain purchase event`() {
val tracker = mockk<AnalyticsTracker>()
val eventSlot = slot<String>()
val paramsSlot = slot<Map<String, Any>>()
every {
tracker.trackEvent(
capture(eventSlot),
capture(paramsSlot)
)
} just Runs // 表示无需返回值
tracker.trackEvent("purchase", mapOf("amount" to 99.9))
assertEquals("purchase", eventSlot.captured)
assertEquals(99.9, paramsSlot.captured["amount"])
}
class UserValidator {
fun isEligible(user: User): Boolean {
// 复杂验证逻辑
return user.age >= 18 && !user.isBanned
}
}
@Test
fun `user should be eligible when meets conditions`() {
val validator = mockk<UserValidator>()
// 使用匹配器组合
every {
validator.isEligible(
match { user ->
user.age >= 18 && user.name.startsWith("A")
}
)
} returns true
val testUser = User(name = "Alice", age = 20)
assertTrue(validator.isEligible(testUser))
}
object NetworkConfig {
fun getBaseUrl() = "https://production.api"
}
@Test
fun `mock singleton object`() {
mockkObject(NetworkConfig)
every { NetworkConfig.getBaseUrl() } returns "https://test.api"
assertEquals("https://test.api", NetworkConfig.getBaseUrl())
unmockkObject(NetworkConfig) // 清理
}
class StringUtils {
companion object {
fun capitalize(str: String) = str.capitalize()
}
}
@Test
fun `mock static method`() {
mockkStatic(StringUtils.Companion::class)
every { StringUtils.capitalize(any()) } returns "MOCKED"
assertEquals("MOCKED", StringUtils.capitalize("hello"))
}
class ProductViewModel(
private val repo: ProductRepository
) : ViewModel() {
private val _products = MutableStateFlow<List<Product>>(emptyList())
val products = _products.asStateFlow()
fun loadProducts() {
viewModelScope.launch {
_products.value = repo.fetchProducts()
}
}
}
@Test
fun `loadProducts should update state`() = runTest {
val repo = mockk<ProductRepository>()
val testProducts = listOf(Product("Mocked Phone"))
coEvery { repo.fetchProducts() } returns testProducts
val viewModel = ProductViewModel(repo)
viewModel.loadProducts()
// 使用 Turbine 库简化 Flow 测试
viewModel.products.test {
assertEquals(emptyList(), awaitItem()) // 初始状态
assertEquals(testProducts, awaitItem())
cancel()
}
}
class StringProvider(private val context: Context) {
fun getAppName() = context.getString(R.string.app_name)
}
@Test
fun `mock context resources`() {
val mockContext = mockk<Context>()
val mockRes = mockk<Resources>()
every { mockContext.resources } returns mockRes
every { mockRes.getString(R.string.app_name) } returns "MockApp"
val provider = StringProvider(mockContext)
assertEquals("MockApp", provider.getAppName())
}
```kotlin
every { spy.shouldMock() } returns false
```
@After
fun tearDown() {
unmockkAll() // 必须清理防止测试污染
}
class OrderService {
private fun internalValidate() { /* ... */ } // 私有方法无法 Mock
}
// 正确做法:重构为 protected 或使用接口
建议在实际项目中:
runTest
)原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。