英文:
Kotlin Unit-Test for combine/collect doesn't collect all items before assert
问题
这个单元测试中有两个 Kotlin 单元测试。第一个可以正常工作,但第二个不能。也许有人知道为什么?
这个使用一个流的 collect 的单元测试可以工作:
@Test
fun testEmission1() = runBlockingTest {
val flow = MutableSharedFlow<Boolean>(replay = 1)
val flow2 = MutableSharedFlow<Boolean>(replay = 1)
val collectedValues = mutableListOf<Boolean>()
val job: Job = launch {
flow.collect {
collectedValues.add(it)
}
}
val job2 = CoroutineScope(testDispatcher).launch {
// WORKS:
flow2.collect {
flow.emit(it)
}
}
flow2.emit(false)
flow2.emit(true)
job.cancel()
job2.cancel()
val expectedValues = listOf(false, true)
assertEquals(expectedValues, collectedValues)
}
但是这个使用两个流的 combine 和 collect 的单元测试,在达到断言语句时只能获取到 collectedValues 中的第一个项并且失败:
@Test
fun testEmission2() = runBlockingTest {
val flow = MutableSharedFlow<Boolean>(replay = 1)
val flow2 = MutableSharedFlow<Boolean>(replay = 1)
val flow3 = MutableSharedFlow<Boolean>(replay = 1)
val collectedValues = mutableListOf<Boolean>()
val job: Job = launch {
flow.collect {
collectedValues.add(it)
}
}
val job2 = CoroutineScope(testDispatcher).launch {
// 不适用 combine/collect:
combine(
flow2,
flow3,
transform = { a, b ->
Pair(a, b)
}
).collect {
val (a, b) = it
flow.emit(a)
}
}
flow3.emit(false)
flow2.emit(false)
flow2.emit(true)
yield() // 似乎没有改变什么
advanceUntilIdle() // 似乎没有改变什么
job.cancel()
job2.cancel()
val expectedValues = listOf(false, true)
assertEquals(expectedValues, collectedValues)
}
我的 testDispatcher 是从单元测试的父类中获取的:
open class BaseCoroutineTest {
@get:Rule
val liveDataThreadTestRule = InstantTaskExecutorRule()
val testDispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun replaceMainDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun resetMainDispatcher() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
@After
fun clearMocks() {
Mockito.framework().clearInlineMocks()
}
}
我尝试向 flow2 中发射了两个值,这应该触发 combine 中的 collect 两次。但是当单元测试达到断言语句时,collectedValues 中只有一个项。问题似乎出现在 combine 函数的行为中。
我使用的是 Kotlin 1.6.10 版本。
英文:
I have here two Kotlin unit-tests. The first one works, but the second doesn't. Maybe someone knows why?
This unit-test with collect on one flow works:
@Test
fun testEmission1() = runBlockingTest {
val flow = MutableSharedFlow<Boolean>(replay = 1)
val flow2 = MutableSharedFlow<Boolean>(replay = 1)
val collectedValues = mutableListOf<Boolean>()
val job: Job = launch {
flow.collect {
collectedValues.add(it)
}
}
val job2 = CoroutineScope(testDispatcher).launch {
// WORKS:
flow2.collect {
flow.emit(it)
}
}
flow2.emit(false)
flow2.emit(true)
job.cancel()
job2.cancel()
val expectedValues = listOf(false, true)
assertEquals(expectedValues, collectedValues)
}
But this unit-test with combine of two flows and collect gets just the first item inside the collectedValues when it reaches the assert statement and fails:
@Test
fun testEmission2() = runBlockingTest {
val flow = MutableSharedFlow<Boolean>(replay = 1)
val flow2 = MutableSharedFlow<Boolean>(replay = 1)
val flow3 = MutableSharedFlow<Boolean>(replay = 1)
val collectedValues = mutableListOf<Boolean>()
val job: Job = launch {
flow.collect {
collectedValues.add(it)
}
}
val job2 = CoroutineScope(testDispatcher).launch {
// doesn't work with combine/collect:
combine(
flow2,
flow3,
transform = { a, b ->
Pair(a, b)
}
).collect {
val (a, b) = it
flow.emit(a)
}
}
flow3.emit(false)
flow2.emit(false)
flow2.emit(true)
yield() // seems to change nothing
advanceUntilIdle() // seems to change nothing
job.cancel()
job2.cancel()
val expectedValues = listOf(false, true)
assertEquals(expectedValues, collectedValues)
}
My testDispatcher comes from my parent class pof the unit-test:
open class BaseCoroutineTest {
@get:Rule
val liveDataThreadTestRule = InstantTaskExecutorRule()
val testDispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun replaceMainDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun resetMainDispatcher() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
@After
fun clearMocks() {
Mockito.framework().clearInlineMocks()
}
}
I tried to emit two values into flow2 which should trigger the collect on the combine two times. But when the unit-test reaches the assert statement there is just one item inside the collectedValues. The problem seems to be in the behaviour of the combine-function.
I'm using Kotlin 1.6.10
答案1
得分: 1
在尝试了几乎一整天的不同方法后,我找到了解决方案。
也许对其他人有用。
我已经更新了以下软件包:
testImplementation "androidx.test:core:1.5.0" // 从 1.4.0
testImplementation "androidx.arch.core:core-testing:2.2.0" // 从 2.1.0
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1" // 从 1.3.0
我将测试分发器更改为 UnconfinedTestDispatcher
:
open class BaseCoroutineTest {
@get:Rule
val liveDataThreadTestRule = InstantTaskExecutorRule()
val testDispatcher = UnconfinedTestDispatcher() // 曾是: TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun replaceMainDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun resetMainDispatcher() {
Dispatchers.resetMain()
// testDispatcher.cleanupTestCoroutines() // 已注释掉
}
@After
fun clearMocks() {
Mockito.framework().clearInlineMocks()
}
}
信息:此更新后应替换 runBlockingTest
函数,因为它已弃用。
英文:
After trying almost a day different approaches, I found a solution.
Maybe it will be useful for someone else.
I've updated the following packages:
testImplementation "androidx.test:core:1.5.0" // from 1.4.0
testImplementation "androidx.arch.core:core-testing:2.2.0" // from 2.1.0
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1" // from 1.3.0
I changed the testDispatcher to UnconfinedTestDispatcher
:
open class BaseCoroutineTest {
@get:Rule
val liveDataThreadTestRule = InstantTaskExecutorRule()
val testDispatcher = UnconfinedTestDispatcher() // was: TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun replaceMainDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun resetMainDispatcher() {
Dispatchers.resetMain()
// testDispatcher.cleanupTestCoroutines() // commented out
}
@After
fun clearMocks() {
Mockito.framework().clearInlineMocks()
}
}
Info: The runBlockingTest
-function should be replaced after this update, because it is deprecated.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论