【发布时间】:2022-01-04 09:22:35
【问题描述】:
我有一个这样的基于回调的 API:
class CallbackApi {
fun addListener(callback: Callback) {
// todo
}
fun removeListener(callback: Callback) {
// todo
}
interface Callback {
fun onResult(result: Int)
}
}
以及将 API 转换为 hot 冷流的扩展函数:
fun CallbackApi.toFlow() = callbackFlow<Int> {
val callback = object : CallbackApi.Callback {
override fun onResult(result: Int) {
trySendBlocking(result)
}
}
addListener(callback)
awaitClose { removeListener(callback) }
}
您是否介意建议如何编写单元测试以确保 API 正确转换为热流?
这是我的尝试。经过反复试验,我想出了这个解决方案。
@Test
fun callbackFlowTest() = runBlocking {
val callbackApi = mockk<CallbackApi>()
val callbackSlot = slot<CallbackApi.Callback>()
every { callbackApi.addListener(capture(callbackSlot)) } just Runs
every { callbackApi.removeListener(any()) } just Runs
val list = mutableListOf<Int>()
val flow: Flow<Int> = callbackApi.toFlow().onEach { list.add(it) }
val coroutineScope = CoroutineScope(this.coroutineContext + SupervisorJob())
flow.launchIn(coroutineScope)
yield()
launch {
callbackSlot.captured.onResult(10)
callbackApi.removeListener(mockk()) // this was a misunderstanding
}.join()
assert(list.single() == 10)
}
但我不明白这个解决方案的两个部分。
1- 如果没有SupervisorJob(),测试似乎永远不会结束。也许出于某种原因收集流量永远不会结束,我不明白。我在一个单独的协程中提供捕获的回调。
2- 如果我删除 callbackSlot.captured.onResult(10) 位于其中的 launch 主体,测试将失败并出现此错误 UninitializedPropertyAccessException: lateinit property captured has not been initialized。我认为yield 应该启动流程。
【问题讨论】:
标签: unit-testing kotlin kotlin-coroutines