如何使用 delay() 调用测试 Kotlin 流程构建器?
How can I test a Kotlin flow builder with delay() calls?
我在一个 Android Kotlin 项目中有一段代码类似于下面,我在其中使用 flow
构建器方法生成一个无限循环的周期性发射:
fun doSomething(): Flow<Int> = flow {
var i = 0
while (true) {
emit(i++)
delay(5000L)
}
}
然后我尝试使用(非常有用!)Turbine 库对该流程进行单元测试,如下所示:
@Test
fun myTest() = runTest {
doSomething().test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
我对 coroutines-test 库中的 runTest
方法的理解是,它会自动跳过该范围内对 delay
的任何调用,但上述测试因超时异常而失败。我试过在整个过程中乱扔日志语句,并且只触发了第一个 assertEquals
调用。后两者从未达到,因为从流程中调用 delay
显然阻止了测试。
这是使用 Flow 构建器时的预期行为吗?如果是这样,在我的场景中有什么方法可以控制时间的流逝吗?
看起来这是 Turbine 0.8.0 和 0.9.0-SNAPSHOT 中的错误。它不继承测试配置,可以跳过延迟。在此处查看问题:https://github.com/cashapp/turbine/issues/84
如果你想现在测试,那么你可以手动进行。
import kotlin.test.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
@OptIn(ExperimentalCoroutinesApi::class)
class DelayTest {
fun doSomething(): Flow<Int> = flow {
var i = 0
while (true) {
println("${System.currentTimeMillis()} emitting $i")
emit(i++)
delay(5000L)
}
}
@Test
fun testFlow() = runTest {
val result = doSomething().take(3).toList()
assertEquals(listOf(0, 1, 2), result)
}
}
或者,作为解决方法,您可以使用 flowOn()
让流程使用测试调度程序。这会改变上下文 upstream 而不会“泄漏下游”。
@Test
fun testFlowOnWorkaround() = runTest {
val testFlow = doSomething()
.flowOn(UnconfinedTestDispatcher(testScheduler))
testFlow.test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
作为另一种解决方法,您可以先将流正常收集到列表中。感谢 runTest{}
延迟被跳过。然后您可以将其转换回流,并测试该流。
@Test
fun testFlowToListWorkaround() = runTest {
val myFlow = doSomething().take(3).toList().asFlow()
myFlow.test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
版本:
- 科特林 1.6.21
- 协程 1.6.1
- Kotlin 测试 1.6.21
我在一个 Android Kotlin 项目中有一段代码类似于下面,我在其中使用 flow
构建器方法生成一个无限循环的周期性发射:
fun doSomething(): Flow<Int> = flow {
var i = 0
while (true) {
emit(i++)
delay(5000L)
}
}
然后我尝试使用(非常有用!)Turbine 库对该流程进行单元测试,如下所示:
@Test
fun myTest() = runTest {
doSomething().test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
我对 coroutines-test 库中的 runTest
方法的理解是,它会自动跳过该范围内对 delay
的任何调用,但上述测试因超时异常而失败。我试过在整个过程中乱扔日志语句,并且只触发了第一个 assertEquals
调用。后两者从未达到,因为从流程中调用 delay
显然阻止了测试。
这是使用 Flow 构建器时的预期行为吗?如果是这样,在我的场景中有什么方法可以控制时间的流逝吗?
看起来这是 Turbine 0.8.0 和 0.9.0-SNAPSHOT 中的错误。它不继承测试配置,可以跳过延迟。在此处查看问题:https://github.com/cashapp/turbine/issues/84
如果你想现在测试,那么你可以手动进行。
import kotlin.test.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
@OptIn(ExperimentalCoroutinesApi::class)
class DelayTest {
fun doSomething(): Flow<Int> = flow {
var i = 0
while (true) {
println("${System.currentTimeMillis()} emitting $i")
emit(i++)
delay(5000L)
}
}
@Test
fun testFlow() = runTest {
val result = doSomething().take(3).toList()
assertEquals(listOf(0, 1, 2), result)
}
}
或者,作为解决方法,您可以使用 flowOn()
让流程使用测试调度程序。这会改变上下文 upstream 而不会“泄漏下游”。
@Test
fun testFlowOnWorkaround() = runTest {
val testFlow = doSomething()
.flowOn(UnconfinedTestDispatcher(testScheduler))
testFlow.test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
作为另一种解决方法,您可以先将流正常收集到列表中。感谢 runTest{}
延迟被跳过。然后您可以将其转换回流,并测试该流。
@Test
fun testFlowToListWorkaround() = runTest {
val myFlow = doSomething().take(3).toList().asFlow()
myFlow.test {
assertEquals(expected = 0, actual = awaitItem())
assertEquals(expected = 1, actual = awaitItem())
assertEquals(expected = 2, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
版本:
- 科特林 1.6.21
- 协程 1.6.1
- Kotlin 测试 1.6.21