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 }
}
}
测试只是挂起,永远不会完成。我尝试了各种方法:launch
或 runBlockingTest
而不是 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
}
}
我正在尝试使用 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 }
}
}
测试只是挂起,永远不会完成。我尝试了各种方法:launch
或 runBlockingTest
而不是 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
}
}