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.Oliver.O
07/19/2022, 10:32 AMrunTest 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