Marcus Ilgner
11/26/2024, 3:31 PMdelay
? There's a function that's supposed to run periodically and I want to check that it's running the expected number of times in a given interval. From the kotest documentation I gathered that I have to set coroutineTestScope = true
.
The additional explanation in the kotlinx-coroutines-test repository suggests that it should be possible to control execution using advanceTimeBy
. But when running my test, all invocations of delay
are just skipped without anyone having called advancedTimeBy
, essentially resulting in an infinite loop that just tries to run the code as quickly as possible.
I have tried adding both testCoroutineScheduler.runCurrent()
and testCoroutineScheduler.advanceUntilIdle()
to indicate that it's supposed to stop afterwards but that didn't make any difference. I suppose that maybe my instance under test runs inside the wrong coroutine context or scope but I don't know which coroutineContext
to pass in.Oliver.O
11/26/2024, 6:59 PMdelay
invocations just define the order in which the invocations complete. Everything then runs as fast as possible (that's what virtual time is for). You should almost never need to invoke test scheduler functions manually.
Of course, not using coroutineTestScope
will get you real-time behavior. But expecting that in tests is not just slow. It also makes them unstable (machine load varies).
So using the test scheduler, you could do the following (this is kotlin-test, but you get the idea):
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
class XTest {
@Test
fun test1() = runTest {
var job1IterationCount = 0
var job2IterationCount = 0
coroutineScope {
launch {
for (i in 1..50) {
delay(10.seconds)
print("1")
job1IterationCount++
}
}
launch {
for (i in 1..5) {
delay(30.seconds)
print("2")
job2IterationCount++
}
}
}
println("\njob1IterationCount=$job1IterationCount, job2IterationCount=$job2IterationCount")
}
}
Because the test dispatcher always operates with a single thread, you don't have to worry about thread-safe mutations.
Does that help?Marcus Ilgner
11/27/2024, 8:48 AMdelay
returns immediately, this essentially becomes an infinite loop.
What's confusing is that the README
of kotlinx-coroutines-test
suggests that delay
can be suspended until advanceTimeBy
is called. If I could get it to work like that, everything would be great.Marcus Ilgner
11/27/2024, 9:17 AMtestScheduler.timeSource.measureTime
as I thought that maybe one needs to explicitly state to use the special TimeSource
. Unfortunately that didn't work either. I think I'll have to postpone this specific test for now. Its ROI is becoming quite bleak otherwise 😬 😅Oliver.O
11/27/2024, 9:28 AMcheck the events / flow items that were produced during that timeCan you make the event cadence also depend on virtual time?
Marcus Ilgner
11/27/2024, 9:31 AMlaunch
its Job. But I can't seem to get delay
to actually wait until the test invokes advanceTimeBy
. Not sure what else might be needed to have it wait on the virtual time being advanced.Oliver.O
11/27/2024, 9:36 AMMarcus Ilgner
11/27/2024, 9:44 AMclass ClassUnderTest(override val coroutineContext: CoroutineContext) : CoroutineScope {
data class Status(val foo: String = "bar")
private val flow =
MutableSharedFlow<Status>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
fun incoming(): Flow<Status> = flow
fun checkCurrentStatus(): Status = Status()
fun monitoringJob(): Job = launch {
while (isActive) {
delay(15.minutes) // actually a configurable interval
flow.emit(checkCurrentStatus())
}
}
}
Now I want to test that when the Job from monitoringJob
is started, a new element is emitted into flow
in the specified intervals.
So after advancing time by 15 minutes, there should be 1 element in the flow, after 30 minutes it should be 2.Oliver.O
11/27/2024, 9:48 AMdelay
for its intervals as well, why shouldn't it just work (without advanceTimeBy
and other scheduler methods)?Marcus Ilgner
11/27/2024, 9:51 AMdelay
in the receiver as well 🤔 up until now, the receiver just collected everything from the flow as fast as possible and I tried using advanceTimeBy
to check what it had collected so far. I'll look into changing it - thanks for the idea!Oliver.O
11/27/2024, 9:53 AM