You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mockk中测试异常抛出及日志方法调用?

问题解决:CoroutineScope协程测试中verify断言失败

问题原因

你的stop()方法使用了**独立的CoroutineScope(Dispatchers.IO)**启动协程,而runTest默认仅跟踪测试自身作用域内的协程。advanceUntilIdle()无法等待这个外部自定义作用域的协程执行完成,导致断言时logError方法还没被调用,从而触发验证失败。

解决方案

方案1:注入CoroutineScope(推荐,解耦测试与业务代码)

将CoroutineScope作为类的依赖注入,测试时使用runTest提供的TestScope替换,让测试能完全控制协程执行。

修改业务代码:

class YourClass(
    // 提供默认实现,不影响生产代码
    private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO),
    private val internalMqttClient: InternalMqttClient,
    private val internalLogger: InternalLogger
) {
    fun stop() {
        println("----")
        coroutineScope.launch {
            try {
                internalMqttClient.disconnectForcibly()
            } catch (e: Exception) {
                internalLogger.logError(DEFAULT_LOG_TAG, "1111")
            }
        }
    }
}

测试代码:

@Test
fun test2() = runTest {
    // 使用当前测试的TestScope注入到目标类
    val testScope = this
    val subject = YourClass(
        coroutineScope = testScope,
        internalMqttClient = mock(),
        internalLogger = mock()
    )

    every { subject.internalMqttClient.disconnectForcibly() } throws Exception()

    subject.stop()
    advanceUntilIdle() // 现在能等待TestScope内的协程执行完毕

    verify(exactly = 1) {
        subject.internalLogger.logError(
            DEFAULT_LOG_TAG,
            "1111"
        )
    }
}

方案2:替换Dispatchers.IO为测试调度器

通过替换IO调度器为runTest提供的测试调度器,让自定义作用域的协程跑在可控制的调度器上。

测试代码修改:

@Test
fun test2() = runTest {
    val testDispatcher = this.testDispatcher
    // 替换IO调度器为测试调度器
    Dispatchers.setIO(testDispatcher)
    try {
        every { internalMqttClient.disconnectForcibly() } throws Exception()

        subject.stop()
        advanceUntilIdle() // 此时能等待IO调度器上的协程执行完成

        verify(exactly = 1) {
            internalLogger.logError(
                DEFAULT_LOG_TAG,
                "1111"
            )
        }
    } finally {
        // 重置调度器,避免影响其他测试用例
        Dispatchers.resetIO()
    }
}

方案3:手动等待自定义作用域完成(不推荐,耦合性高)

如果无法修改业务代码,可通过暴露或反射获取自定义CoroutineScope的Job,手动等待其执行完毕:

@Test
fun test2() = runTest {
    every { internalMqttClient.disconnectForcibly() } throws Exception()

    subject.stop()
    // 假设你的类暴露了coroutineScope属性
    subject.coroutineScope.joinAll()
    advanceUntilIdle()

    verify(exactly = 1) {
        internalLogger.logError(
            DEFAULT_LOG_TAG,
            "1111"
        )
    }
}

内容的提问来源于stack exchange,提问作者Bob Redity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:55:08