【问题标题】:Kotlin Flow: Testing hangsKotlin Flow:测试挂起
【发布时间】:2020-05-13 10:36:46
【问题描述】:

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

【问题讨论】:

  • 您是否尝试在其他调度程序上启动,只是为了调试目的?
  • 是的,我做到了。没有效果。顺便说一句,我尝试了新发布的 StateFlow/MutableStateFlow 问题仍然存在

标签: kotlin kotlin-flow kotlin-coroutines kotest


【解决方案1】:

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

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

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

这就是您的测试与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
    }
}

【讨论】:

  • 如果我们整个测试函数都标记为runBlocking呢?
  • @IgorGanapolsky 我相信在这种情况下整个测试功能也会挂起。我们如何处理生产代码在流和通道上使用collect的这种情况?
  • @TomášHavlíček collect 不会挂起,因为它是异步的
  • @IgorGanapolsky 收集暂停直到流程结束
猜你喜欢
  • 1970-01-01
  • 2020-02-22
  • 1970-01-01
  • 2014-04-15
  • 2013-04-12
  • 1970-01-01
  • 1970-01-01
  • 2022-10-04
  • 1970-01-01
相关资源
最近更新 更多