Kotlin Android单元测试MockK指南

发布于:2025-05-16 ⋅ 阅读:(11) ⋅ 点赞:(0)

目录

  1. MockK 简介
  2. 环境配置
  3. 基础用法
  4. 高级用法
  5. Android 特有场景
  6. 最佳实践

1. MockK 简介

MockK 是一个专为 Kotlin 设计的 Mocking 框架,支持协程、扩展函数、对象声明(object)等 Kotlin 特性。相比 Mockito,它提供更自然的 Kotlin API,解决了 final 类无法 Mock 的问题。

核心优势

  • 原生支持 Kotlin 特性(如协程、object 单例)。
  • 简洁的 DSL 语法。
  • 支持静态方法、构造函数 Mock。

2. 环境配置

build.gradle 中添加依赖:

// 模块级 build.gradle
dependencies {
    testImplementation "io.mockk:mockk:1.13.8"        // 基础库
    testImplementation "io.mockk:mockk-agent-jvm:1.13.8" // 解决某些 JDK 版本兼容性问题
    testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" // JUnit 5(可选)
}

注意:若使用 JUnit 4,需添加 testImplementation "io.mockk:mockk-android:1.13.8"


3. 基础用法

3.1 创建 Mock 对象
val service = mockk<MyService>() // 创建 Mock 对象
3.2 设置行为 (Stubbing)
// 模拟方法返回值
every { service.fetchData(any()) } returns "Mocked Data"

// 模拟抛出异常
every { service.fail() } throws RuntimeException("Error")
3.3 验证调用
service.fetchData(123)
verify { service.fetchData(123) } // 验证方法是否被调用

// 验证调用次数
verify(exactly = 1) { service.fetchData(any()) }
3.4 参数匹配器
// 匹配任何参数
every { service.fetchData(any()) } returns "Data"

// 捕获参数
val slot = slot<Int>()
every { service.saveData(capture(slot)) } just Runs

service.saveData(123)
assert(slot.captured == 123)

4. 高级用法

4.1 模拟静态方法与对象声明
// Mock object 单例
object Singleton {
    fun doWork() = "Real"
}

mockkObject(Singleton)
every { Singleton.doWork() } returns "Mocked"

// Mock 静态方法
mockkStatic(MyUtils::class)
every { MyUtils.format(any()) } returns "Formatted"

清理资源

@After
fun tearDown() {
    unmockkAll() // 或 unmockkObject(Singleton)
}
4.2 模拟构造函数
class MyHelper(val config: String)

mockkConstructor(MyHelper::class)
every { anyConstructed<MyHelper>().config } returns "Mocked Config"

val helper = MyHelper("Real")
assert(helper.config == "Mocked Config")
4.3 协程支持

使用 coEverycoVerify 处理挂起函数:

class Repository {
    suspend fun loadData() = "Real Data"
}

val repo = mockk<Repository>()
coEvery { repo.loadData() } returns "Mocked Data"

runBlocking {
    val data = repo.loadData()
    assert(data == "Mocked Data")
    coVerify { repo.loadData() }
}
4.4 Spy:部分模拟真实对象
val spy = spyk<RealClass>() // 默认调用真实方法

every { spy.mockedMethod() } returns "Mocked"

5. Android 特有场景

5.1 测试 ViewModel
class MyViewModel(private val repo: Repository) : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun load() {
        viewModelScope.launch {
            _data.value = repo.fetchData()
        }
    }
}

// 测试代码
@Test
fun testViewModel() = runTest { // 使用 TestCoroutineDispatcher
    val repo = mockk<Repository>()
    coEvery { repo.fetchData() } returns "Test Data"

    val viewModel = MyViewModel(repo)
    viewModel.load()

    // 处理 LiveData
    val observer = mockk<Observer<String>>()
    viewModel.data.observeForever(observer)
    verify(timeout = 1000) { observer.onChanged("Test Data") }
}
5.2 处理 Context
val context = mockk<Context>()
val res = mockk<Resources>()

every { context.resources } returns res
every { res.getString(any()) } returns "Mocked String"

6. 最佳实践

  1. 避免过度 Mock:仅 Mock 外部依赖(如网络、数据库),不要 Mock 被测类内部行为。
  2. 使用清晰命名:如 userRepositoryMock 替代 mockk<Repository>()
  3. 及时清理:在 @After 中调用 unmockkAll() 避免测试间污染。
  4. 组合使用工具:结合 TruthTurbine 等库提升断言可读性。
  5. 优先使用真实对象:当依赖简单且无副作用时,直接使用真实对象而非 Mock。

实际开发中,建议结合具体场景选择最合适的 Mock 策略,确保测试既简洁又可靠。


网站公告

今日签到

点亮在社区的每一天
去签到