I am trying to write class which will get some current data every some time so it will transform one flow into another. I go stuck while writing tests for it.
When I run one test it passes but when I run all of them: testA always passes but testB or testC fails with this error:
kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing, there were active child jobs
Classes:
interface Time {
val currentTime: Flow<Int>
}
data class Data(val value: Int)
interface DataRepository {
fun getCurrentData(): Data
}
My main class:
@Singleton
class CurrentDataProvider(time: Time, repository: DataRepository) {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private var lastUpdate = Int.MIN_VALUE
val currentData: SharedFlow<Data> =
time.currentTime
.transform {
if (lastUpdate + 10 <= it) {
lastUpdate = it
emit(repository.getCurrentData())
}
}
.onCompletion {
coroutineScope.cancel() // Is it necessary?
}
.shareIn(
scope = coroutineScope,
started = SharingStarted.Eagerly,
)
}
And tests:
class Test {
private val time = mock<Time>()
private val data = Data(123)
private val repository = mock<DataRepository>{
on { getCurrentData() } doReturn data
}
@Test
fun `testA`() = runTest {
// given
whenever(time.currentTime).doReturn(flowOf(0))
// when
val tested = CurrentDataProvider(time, repository)
// then
tested.currentData.test {
val item = awaitItem()
item shouldBe data
}
verify(repository, times(1)).getCurrentData()
}
@Test
fun `testB`() = runTest {
// given
whenever(time.currentTime).doReturn(flowOf(0, 1, 2, 3))
// when
val tested = CurrentDataProvider(time, repository)
// then
tested.currentData.test {
val item = awaitItem()
item shouldBe data
}
verify(repository, times(1)).getCurrentData()
}
@Test
fun `testC`() = runTest {
// given
whenever(time.currentTime).doReturn(flowOf(1, 2, 3, 11))
// when
val tested = CurrentDataProvider(time, repository)
// then
tested.currentData.test {
awaitItem()
val item = awaitItem()
item shouldBe data
}
verify(repository, times(2)).getCurrentData()
}
I tried to:
- add
withContext(Dispatchers.Default)
- add
MainDispatcherRule
:
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@OptIn(ExperimentalCoroutinesApi::class)
class MainDispatcherRule(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
Dispatchers.resetMain()
}
}
And it still fails every ~5th test.