Kotlin Flow:测试挂起

Kotlin Flow: Testing hangs

我正在尝试使用 Flows 测试 Kotlin 实现。我使用 Kotest 进行测试。此代码有效:

视图模型:

val detectedFlow = flow<String> {
    emit("123")
    delay(10L)
    emit("123")
}

测试:

class ScanViewModelTest : StringSpec({
    "when the flow contains values they are emitted" {
        val detectedString = "123"
        val vm = ScanViewModel()
        launch {
            vm.detectedFlow.collect {
                it shouldBe detectedString
            }
        }
    }
})

然而,在真实的ViewModel中我需要给流添加值,所以我使用ConflatedBroadcastChannel如下:

private val _detectedValues = ConflatedBroadcastChannel<String>()
val detectedFlow = _detectedValues.asFlow()

suspend fun sendDetectedValue(detectedString: String) {
    _detectedValues.send(detectedString)
}

然后在测试中我尝试:

"when the flow contains values they are emitted" {
    val detectedString = "123"
    val vm = ScanViewModel()
    runBlocking {
        vm.sendDetectedValue(detectedString)
    }
    runBlocking {
        vm.detectedFlow.collect { it shouldBe detectedString }
    }
}

测试只是挂起,永远不会完成。我尝试了各种方法:launchrunBlockingTest 而不是 runBlocking,将发送和收集放在相同或不同的协程中,offer 而不是 send。 .. 似乎没有什么可以解决的。我做错了什么?

更新:如果我手动创建流程,它会起作用:

private val _detectedValues = ConflatedBroadcastChannel<String>()
val detectedFlow =  flow {
    this.emit(_detectedValues.openSubscription().receive())
}

所以,这是 asFlow() 方法中的错误吗?

问题是您在测试中使用的 collect 函数是一个暂停函数,它将暂停执行直到 Flow 完成。

在第一个示例中,您的 detectedFlow 是有限的。它只会发出两个值并完成。在您的问题更新中,您还创建了一个有限流,它将发出一个值并完成。这就是您的测试有效的原因。

但是,在第二个(现实生活中的)示例中,流是从 ConflatedBroadcastChannel 永远不会关闭的 创建的。因此 collect 函数永远暂停执行。为了使测试工作而不会永远阻塞线程,您也需要使流程有限。为此,我通常使用 first() 运算符。另一种选择是 close ConflatedBroadcastChannel 但这通常意味着仅仅因为测试而修改您的代码,这不是一个好的做法。

这就是您的测试如何使用 first() 运算符

"when the flow contains values they are emitted" {
    val detectedString = "123"
    val vm = ScanViewModel()
    runBlocking {
        vm.sendDetectedValue(detectedString)
    }
    runBlocking {
        vm.detectedFlow.first() shouldBe detectedString
    }
}