How do I pass my own test dispatcher to the `testA...
# ktor
u
How do I pass my own test dispatcher to the
testApplication
at test time?
Copy code
class FooTest {
    ...
    private val scheduler = TestCoroutineScheduler()
    private val standardTestDispatcher = StandardTestDispatcher(scheduler)
    private val dispatcherProvider = TestDispatcherProvider(standardTestDispatcher) <---------
    private val appDatabase = InMemoryDatabase(...)
    private val fooDao = FooDao(dispatcherProvider, appDatabase.fooQueries) <-----------

    @Test
    fun foo() = testApplication(standardTestDispatcher) { <---------
        turbineScope {
            val fooTurbine = fooDao.foo.testIn(this)
            assertThat(fooTurbine.awaitItem()).isNull()
            fooDao.saveFoo(..)
            assertThat(fooTurbine.awaitItem()).isNotNull()
            fooTurbine.cancelAndEnsureAllEventsConsumed()
        }
    }
}

class TestDispatcherProvider(standardTestDispatcher: TestDispatcher) : DispatcherProvider {
    override val io: CoroutineDispatcher = standardTestDispatcher
    override val main: CoroutineDispatcher = standardTestDispatcher
    override val default: CoroutineDispatcher = standardTestDispatcher
}
crashes with
Copy code
java.lang.IllegalStateException: Detected use of different schedulers. If you need to use several test coroutine dispatchers, create one `TestCoroutineScheduler` and pass it to each of them.
        at kotlinx.coroutines.test.TestCoroutineSchedulerKt.checkSchedulerInContext(TestCoroutineScheduler.kt:257)
        at kotlinx.coroutines.test.TestCoroutineScheduler.registerEvent$kotlinx_coroutines_test(TestCoroutineScheduler.kt:68)
        at kotlinx.coroutines.test.StandardTestDispatcherImpl.dispatch(TestCoroutineDispatchers.kt:150)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.safeDispatch(DispatchedContinuation.kt:254)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:318)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:170)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
        at io.ktor.server.testing.TestApplicationKt.runTestApplication(TestApplication.kt:534)
        at io.ktor.server.testing.TestApplicationKt$testApplication$1.invokeSuspend(TestApplication.kt:517)
        at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42)
        at io.ktor.test.dispatcher.TestCommonKt$runTestWithRealTime$1.invokeSuspend(TestCommon.kt:40)
        at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$1.invokeSuspend(TestBuilders.kt:317)
        Caused by:
        java.lang.IllegalStateException: Detected use of different schedulers. If you need to use several test coroutine dispatchers, create one `TestCoroutineScheduler` and pass it to each of them.
            at kotlinx.coroutines.test.TestCoroutineSchedulerKt.checkSchedulerInContext(TestCoroutineScheduler.kt:257)
            at kotlinx.coroutines.test.TestCoroutineScheduler.registerEvent$kotlinx_coroutines_test(TestCoroutineScheduler.kt:68)
            at kotlinx.coroutines.test.StandardTestDispatcherImpl.dispatch(TestCoroutineDispatchers.kt:150)
            at kotlinx.coroutines.internal.DispatchedContinuationKt.safeDispatch(DispatchedContinuation.kt:254)
            at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:318)
            at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
            at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:170)
            at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
            at io.ktor.server.testing.TestApplicationKt.runTestApplication(TestApplication.kt:534)
            at io.ktor.server.testing.TestApplicationKt$testApplication$1.invokeSuspend(TestApplication.kt:517)
            ....
Such pattern does work with
runTest(standardTestDispatcher)
a
You can use the
Dispatchers.setMain
to override the dispatcher for the tests. You can find a more detailed explanation here.
u
I'm a bit confused, in the link they say it is also possible to pass a dispatcher param to the runTest thar different from passing it into testApplication? I though testApplication wraps runTest
a
Unfortunately, there is no way to pass the coroutine context from the call of
testApplication
to the
runTest
.
u
hm, how can I verify it maybe? test doesnt throw anymore but seem to be nondeterministic for some reason
a
Can you explain what do you need to verify?
u
if it is picking up the dispatcher, since "inside" I have a
combine
flow operator, and it sometimes seems to emit in different order, and then turbine assertion fails which leads me to believe its not getting picked up unless I dont know how
StandardTestDispatcher
works..I thought it's singlethreadeness is what makes it deterministic
a
You can verify it by comparing the scheduler object references:
Copy code
private val scheduler = TestCoroutineScheduler()
private val standardTestDispatcher = StandardTestDispatcher(scheduler)

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun test() {
    println(standardTestDispatcher)
    Dispatchers.setMain(standardTestDispatcher)
    try {
        testApplication {
            println(currentCoroutineContext())
        }
    } finally {
        Dispatchers.resetMain()
    }
}
u
Copy code
CURRENT=[
	RunningInRunTest, 
	kotlinx.coroutines.test.TestCoroutineScheduler@2b8427f8, 
	kotlinx.coroutines.test.TestScopeKt$TestScope$$inlined$CoroutineExceptionHandler$1@3bd86d89, 
	ScopeCoroutine{Active}@bff2862, 
	Dispatchers.Default.limitedParallelism(1)
]
DESIRED=StandardTestDispatcher[scheduler=kotlinx.coroutines.test.TestCoroutineScheduler@2b8427f8]
are you expecting the
dispatcher
to be there (or scheduler)?
since inside
Copy code
@KtorDsl
public fun testApplication(
    parentCoroutineContext: CoroutineContext = EmptyCoroutineContext,
    block: suspend ApplicationTestBuilder.() -> Unit
): TestResult = runTestWithRealTime {
    runTestApplication(parentCoroutineContext, block)
}
public fun runTestWithRealTime(
    context: CoroutineContext = EmptyCoroutineContext,
    timeout: Duration = 60.seconds,
    testBody: suspend CoroutineScope.() -> Unit
): TestResult = runTest(context, timeout) {
    withContext(Dispatchers.Default.limitedParallelism(1), testBody)
}
...so it's not using the standard dispatcher, since it's referencing
Disp.Default
which is not getting replaced?
a
I see the following lines in the console after executing my test:
Copy code
StandardTestDispatcher[scheduler=kotlinx.coroutines.test.TestCoroutineScheduler@60099951]
[RunningInRunTest, kotlinx.coroutines.test.TestCoroutineScheduler@60099951, kotlinx.coroutines.test.TestScopeKt$TestScope$$inlined$CoroutineExceptionHandler$1@3f11b1e0, ScopeCoroutine{Active}@3eff175e, Dispatchers.Default.limitedParallelism(1)]
u
yes that matches my prints
and im asking if it enough for it to reference the same
scheduler
, and not
dispatcher
as well
a
I don't think it's enough, but unfortunately, only the scheduler can be overridden at the moment.