【发布时间】:2022-07-15 21:16:14
【问题描述】:
我正在测试一个阻塞的协程。这是我的生产代码:
interface Incrementer {
fun inc()
}
class MyViewModel : Incrementer, CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
private val _number = MutableStateFlow(0)
fun getNumber(): StateFlow<Int> = _number.asStateFlow()
override fun inc() {
launch(coroutineContext) {
delay(100)
_number.tryEmit(1)
}
}
}
我的测试:
class IncTest {
@BeforeEach
fun setup() {
Dispatchers.setMain(StandardTestDispatcher())
}
@AfterEach
fun teardown() {
Dispatchers.resetMain()
}
@Test
fun incrementOnce() = runTest {
val viewModel = MyViewModel()
val results = mutableListOf<Int>()
val resultJob = viewModel.getNumber()
.onEach(results::add)
.launchIn(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
launch(StandardTestDispatcher(testScheduler)) {
viewModel.inc()
}.join()
assertEquals(listOf(0, 1), results)
resultJob.cancel()
}
}
我将如何测试我的 inc() 函数? (接口是一成不变的,所以我不能把 inc() 变成一个挂起函数。)
【问题讨论】:
-
它失败了,因为我相信你不会在这段代码的任何地方等待发射。
inc()不会等待,所以join()也不会等待,然后直接进入断言。但老实说,我很难理解您在这里尝试实现的目标。您尝试等待生产者完成,但在消费者端验证结果。即使生产者发出了一个项目,我们也不能保证消费者已经消费了它。我认为你应该等待消费者,而不是生产者,例如假设正好有 2 个项目要消费或在发射后关闭流程。 -
@broot 我想测试是否实际调用了生产者,并且在 resultJob 中收集的结果是否正确。我真的需要阻止测试,直到在 inc() 中启动的作业完成。我怀疑我需要通过测试调度程序,但我不知道如何。
-
如果您需要阻止
inc()直到它完成,那么在其中使用runBlocking()而不是launch()。您在代码中使用了很多启动,这使得等待任何事情变得非常困难。尽管如此,我相信即使您等待inc()完成,您也不能保证同时运行的收集器/消费者已经消耗了该项目。即使在模拟测试环境中运行时这是确定性的,它也可能在实际应用程序中失败。
标签: unit-testing kotlin kotlin-coroutines