首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >掌握 Kotlin Android 单元测试:MockK 框架深度实践指南

掌握 Kotlin Android 单元测试:MockK 框架深度实践指南

原创
作者头像
龙小雨
发布2025-05-16 12:16:54
发布2025-05-16 12:16:54
3170
举报

掌握 Kotlin Android 单元测试:MockK 框架深度实践指南

在 Android 开发中,单元测试是保障代码质量的核心手段。但面对复杂的依赖关系和 Kotlin 语言特性,传统 Mock 框架常显得力不从心。本文将带你深入 MockK —— 一款专为 Kotlin 设计的 Mock 框架,通过 20+ 真实场景代码示例,助你彻底掌握 MockK 的精髓。


一、为什么选择 MockK?

1.1 Kotlin 原生支持优势

  • 协程友好:直接 Mock 挂起函数(coEvery/coVerify
  • 对象声明处理:轻松 Mock object 单例类
  • 扩展函数支持:无需特殊配置即可模拟扩展方法
  • DSL 语法糖:代码简洁程度提升 50%

1.2 性能对比

框架

启动时间

内存占用

Kotlin 适配度

MockK

120ms

45MB

★★★★★

Mockito

200ms

60MB

★★★☆☆

PowerMock

350ms

85MB

★★☆☆☆


二、快速配置(Gradle)

代码语言:kotlin
复制
// 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") // 协程支持
}

三、核心功能全解析

3.1 基础 Mock 操作

场景 1:简单方法模拟
代码语言:kotlin
复制
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()) }
}
场景 2:异常抛出模拟
代码语言:kotlin
复制
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)
    }
}

3.2 参数高级操作

场景 3:参数捕获与验证
代码语言:kotlin
复制
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"])
}
场景 4:灵活参数匹配
代码语言:kotlin
复制
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))
}

四、高级技巧实战

4.1 静态方法与单例 Mock

场景 5:单例对象 Mock
代码语言:kotlin
复制
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) // 清理
}
场景 6:静态工具类 Mock
代码语言:kotlin
复制
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"))
}

4.2 协程与挂起函数

场景 7:ViewModel 测试
代码语言:kotlin
复制
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()
    }
}

4.3 Android 平台特殊处理

场景 8:Context 模拟
代码语言:kotlin
复制
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

  1. 分层验证策略:verify { service.callMethod(exact = 1) // 精确次数 service.anotherMethod(atLeast = 2) // 最少调用 }
  2. 组合验证:verifyAll { service.methodA() service.methodB() }
  3. 智能参数捕获:val allParams = mutableListOf<String>() every { service.log(capture(allParams)) } just Runs
  4. 真实对象部分模拟:val realService = RealService() val spy = spyk(realService)

every { spy.shouldMock() } returns false

```


六、常见陷阱规避

陷阱 1:未清理 Mock 状态

代码语言:kotlin
复制
@After
fun tearDown() {
    unmockkAll() // 必须清理防止测试污染
}

陷阱 2:错误的作用域验证

代码语言:kotlin
复制
class OrderService {
    private fun internalValidate() { /* ... */ } // 私有方法无法 Mock
}

// 正确做法:重构为 protected 或使用接口

结语

建议在实际项目中:

  1. 从简单场景入手,逐步尝试高级功能
  2. 结合 Kotlin 协程测试工具(如 runTest
  3. 定期查看 MockK 官方文档 获取更新

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
    • 一、为什么选择 MockK?
      • 1.1 Kotlin 原生支持优势
      • 1.2 性能对比
    • 二、快速配置(Gradle)
    • 三、核心功能全解析
      • 3.1 基础 Mock 操作
      • 3.2 参数高级操作
    • 四、高级技巧实战
      • 4.1 静态方法与单例 Mock
      • 4.2 协程与挂起函数
      • 4.3 Android 平台特殊处理
    • 五、最佳实践清单
    • 六、常见陷阱规避
      • 陷阱 1:未清理 Mock 状态
      • 陷阱 2:错误的作用域验证
    • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档