2

我正在尝试使用 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()方法中的错误吗?

4

1 回答 1

9

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

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

但是,在第二个(现实生活)示例中,流是从ConflatedBroadcastChannel 从未关闭的 a 创建的。因此,该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
    }
}
于 2020-06-07T07:31:10.110 回答