-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Milestone
Description
Goals
Support running suspend test in JUnit Jupiter:
class Kit {
@Test
suspend fun foo() {
delay(1000) // suspend call
assertEquals(1, 1)
}
}
Currently, such test can be written this way:
class Kit {
@Test
fun foo() = runBlocking {
delay(1000) // suspend call
assertEquals(1, 1)
assertThrows { /* ... */ }
Unit // test should return void, but `assertThrows` returns `Throwable`, so `foo` returns `Throwable` too
}
}
Also, will be nice to provide CoroutineScope
through params, or as receiver in extension:
class Kit {
suspend fun foo(scope: CoroutinesScope) { /* ... */ } // (1)
suspend fun CoroutinesScope.foo() { /* ... */ } // (2)
}
1 and 2 actually the same on bytecode level. suspend
is optional.
And finally, support for runBlockingTest
:
class Kit {
suspend fun foo(scope: TestCoroutinesScope) { /* ... */ }
suspend fun TestCoroutinesScope.foo() { /* ... */ }
}
What can be done currently
ParameterResolver
can be used to provide stubs for Continuation
, CoroutineScope
and TestCoroutineScope
. These stub arguments can be replaced with real arguments in invocation.
Problems
Current extensions points not enough to implement this feature as extensions, since:
- Discovery. Jupiter discovers tests that returns
void
, butsuspend fun
returnsObject
; - Invocation.
InvocationInterceptor
in 5.5-M1(SNAPSHOT) don't providing mechanism to override actual invocation, only to decoration of existing invocation. Conversion ofmethod
tokotlinFunction
, and then executing usingcallSuspend
is necessary to executesuspend fun
.
Also, my slides about this topic.
IljaKroonen, kimble, cristianoperez, adamszadkowski, frost13it and 93 moredzikoysk, hpedrorodrigues and keychera
Metadata
Metadata
Assignees
Type
Projects
Status
Done
Activity
sormuras commentedon Jun 4, 2019
Related to #1851
marcphilipp commentedon Mar 19, 2020
@IRus This is not resolved by #2042, is it?
IRus commentedon Mar 20, 2020
@marcphilipp Yes, not resolved, this is different issues
xeruf commentedon Feb 4, 2021
Please resolve this! My tests looked all fine until I discovered that they were not run at all anymore after adding suspending functions :/
34 remaining items
JavierSegoviaCordoba commentedon May 19, 2025
@marcphilipp which is the reason to use
runBlocking
overrunTest
?marcphilipp commentedon May 19, 2025
You're referring to https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html, right? I wasn't sure it was a good fit because it returns
TestResult
which is not consumable (unless you manually userunTest
) and it skips delays which I thought would be rather surprising. It may be possible to convince me to change it, though, if you have good arguments. 🙂rgmz commentedon May 19, 2025
Thanks for adding this, @marcphilipp!
IMO fast-forwarding delays is desirable behaviour. I don't want, or necessarily need, my ExponentialBackOff test cases to actually take 2/4/8/16/32/64/128... seconds.
IRus commentedon May 19, 2025
Using
runTest
would imply passing aTestScope
to the test functions somehow; otherwise, the user can’t call methods like advanceTimeBy.Specifying
TestScope
also requires more or the same syntax than simply writingrunTest
.Additionally, this would imply that
suspend
tests would only work if the user explicitly provideskotlinx-coroutines-test
, or it must be included injunit-platform-common
, which I assume is undesirable.opatry commentedon May 19, 2025
Also, having
TestScope
being provided to the test would allow usingadvanceUntilIdle
and stuff like that which is very desirable also.ephemient commentedon May 19, 2025
I would have used
kotlin.coroutines.jvm.internal.runSuspend
instead ofkotlinx.coroutines.runBlocking
- it's in kotlin-stdlib-jvm so it's always present, and if a user wants a realCoroutineScope
orTestScope
they can userunBlocking
orrunTest
themselves.JavierSegoviaCordoba commentedon May 20, 2025
@marcphilipp if I remember correctly, it is highly recommended to use "real"
delay
on tests instead of utility functions to add waits and/or move forward in time. Maybe @dkhalanskyjb can help with this, as I vaguely remember his comments.runBlocking
would make those tests too slow, sorunTest
is the only way to go. Additionally it is necessary to be able to change the dispatcher somehow.It would be amazing if we could do something like this annotation to set the dispatcher for the whole test class:
Even I would find a bit weird to need to replace the dispatcher only on a single test instead of the whole class,
@TestDispatcher(FooDispatcher::class)
should work if it is annotating a test method too.dkhalanskyjb commentedon May 20, 2025
When possible (which is most of the time), yes, that's the recommended approach: Kotlin/kotlinx.coroutines#3919
marcphilipp commentedon May 23, 2025
@JavierSegoviaCordoba Could you please create a new issue for
runTest
and@TestDispatcher
?suspend
test should userunTest
. #4768JavierSegoviaCordoba commentedon Jul 15, 2025
@marcphilipp created here: #4768
IRus commentedon Jul 15, 2025
@marcphilipp I think @ephemient having a very good point about using
runSuspend
(which is internal, but implementation is very straightforward and can be re-implemented easily).runBlocking
doing some clever things internally: https://madhead.me/posts/chm-not-concurrent-runblocking-not-blocking/And
runSuspend
just blocking current thread until coroutine finished without any extra hidden behaviorIRus commentedon Jul 17, 2025
Another nice property of
runSuspend
- it can be implemented without kotlinx.corountes, so JUnit wouldn't imply runtime dependency on kotlinx-coroutines-core. Which of course likely be present for people that want to use suspend tests, but not having plus one dependency is usually good thingmarcphilipp commentedon Jul 17, 2025
Let's please continue the discussion in #4768