Stephen Edwards
07/18/2022, 5:54 PMkotlinx.coroutines.test
migration guide for 1.6+ when talking about replacing runBlockingTest
with runTest
there is a little line:
It works properly with other dispatchers and asynchronous completions.
No action on your part is required, other than replacing runBlocking with runTest as well.
What is meant by this? Why does runBlocking
need to be replaced with runTest
if there is no dispatcher/virtual time manipulation?Dmitry Khalanskiy [JB]
07/19/2022, 9:26 AMrunBlocking
, it's just a bit less convenient:
• There's no runBlocking
on JS, so the test won't be usable in multiplatform code. This was the message behind the line: 1.6 introduced multiplatform coroutine testing.
• runBlocking
returns the value that its block returns. JUnit, for example, expects the test functions to return Unit
. So, something like @Test fun test() = runBlocking { assertFailsWith<IllegalArgumentException> { throw IllegalArgumentException() } }
will, surprisingly, not be executed by JUnit, because test()
returns an IllegalArgumentException
.
• runTest
is purely for tests, which means we can enhance it with other niceties that have no place in production code. For example, we recently added a way to cancel some of the coroutines in a test when the test body finishes: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/background-scope.html You won't have that with runBlocking
.Oliver.O
07/19/2022, 9:39 AMrunTest
implementation is that you cannot run multithreaded (stress) tests with a standard dispatcher. For example: runTest(Dispatchers.Default) { ... }
produces IllegalArgumentException: Dispatcher must implement TestDispatcher: Dispatchers.Default
.Dmitry Khalanskiy [JB]
07/19/2022, 9:46 AMrunTest { launch(Dispatchers.Default) { ... } }
or runTest { withContext(Dispatchers.Default) { ... } }
.Oliver.O
07/19/2022, 9:57 AMTestDispatcher
(as shown) conflict with some implementation dependency as runTest
internals evolve over time? If not, would you consider making runTest
accept a non-TestDispatcher
directly and document that behavior?Dmitry Khalanskiy [JB]
07/19/2022, 10:13 AMkotlinx-coroutines-test
is made entirely in-house. However, I think it would be misleading API-wise to allow passing a non-TestDispatcher
to runTest
, because then the functions provided by TestScope
(in which runTest
runs its blocks) don't make sense if the dispatcher that runs the test body is not integrated with the test module. By forcing the framework users to do withContext
manually, we make this explicit: hey, you're no longer in the TestScope
and your code does not play by the same rules.
I would write a wrapper such as this if you need such behavior often:
@OptIn(ExperimentalStdlibApi::class)
public fun runStressTest(
context: CoroutineContext = Dispatchers.Default,
testBody: suspend CoroutineScope.() -> Unit
): TestResult = runTest(context.minusKey(CoroutineDispatcher), dispatchTimeoutMs = Long.MAX_VALUE) {
withContext(context, testBody)
}
Oliver.O
07/19/2022, 10:29 AMrunTest
is implemented, which currently works if I switch to another dispatcher but might not work in a future version.runTest
could possibly check for leaking coroutines at the end. I did not look into specifics, though.Dmitry Khalanskiy [JB]
07/19/2022, 10:36 AM