Reputation: 4605
I've created a multi-platform Kotlin project (JVM & JS), declared an expected class and implemented it:
// Common module:
expect class Request(/* ... */) {
suspend fun loadText(): String
}
// JS implementation:
actual class Request actual constructor(/* ... */) {
actual suspend fun loadText(): String = suspendCoroutine { continuation ->
// ...
}
}
Now I'm trying to make a unit test using kotlin.test
, and for the JVM platform I simply use runBlocking
like this:
@Test
fun sampleTest() {
val req = Request(/* ... */)
runBlocking { assertEquals( /* ... */ , req.loadText()) }
}
How can I reproduce similar functionality on the JS platform, if there is no runBlocking
?
Upvotes: 11
Views: 1456
Reputation: 5191
GlobalScope.promise { ... }
.runTest { ... }
(from kotlinx-coroutines-test
), which is cross-platform, and has some other benefits over runBlocking { ... }
and GlobalScope.promise { ... }
as well.I'm not sure what things were like when the question was originally posted, but nowadays the standard, cross-platform way to run tests that use suspend
functions is to use runTest { ... }
(from kotlinx-coroutines-test
).
Note that in addition to running on all platforms, this also includes some other features, such as skipping delay
s (with the ability to mock the passage of time).
If for any reason (which is not typical, but might sometimes be the case) it is actually desirable to run the code in the test as it runs in production (including actual delay
s), then runBlocking { ... }
can be used on JVM and Native, and GlobalScope.promise { ... }
on JS. If going for this option, it might be convenient to define a single function signature which uses runBlocking
on JVM and Native, and GlobalScope.promise
on JS, e.g.:
// Common:
expect fun runTest(block: suspend CoroutineScope.() -> Unit)
// JS:
@OptIn(DelicateCoroutinesApi::class)
actual fun runTest(block: suspend CoroutineScope.() -> Unit): dynamic = GlobalScope.promise(block=block)
// JVM, Native:
actual fun runTest(block: suspend CoroutineScope.() -> Unit): Unit = runBlocking(block=block)
Upvotes: 2
Reputation: 8324
I was able to make the following work:
expect fun coTest(timeout: Duration = 30.seconds, block: suspend () -> Unit): Unit
// jvm
actual fun coTest(timeout: Duration, block: suspend () -> Unit) {
runBlocking {
withTimeout(timeout) {
block.invoke()
}
}
}
// js
private val testScope = CoroutineScope(CoroutineName("test-scope"))
actual fun coTest(timeout: Duration, block: suspend () -> Unit): dynamic = testScope.async {
withTimeout(timeout) {
block.invoke()
}
}.asPromise()
This launches a co-routine in a scope of your choice using async
which you can then return like a promise.
You then write a test like so:
@Test
fun myTest() = coTest {
...
}
Upvotes: 0
Reputation: 1550
Mb it's late, but there are open issue for adding possibility to use suspend
functions in js-tests (there this function will transparent convert to promise)
Workaround:
One can define in common code:
expect fun runTest(block: suspend () -> Unit)
that is implemented in JVM with
actual fun runTest(block: suspend () -> Unit) = runBlocking { block() }
and in JS with
actual fun runTest(block: suspend () -> Unit): dynamic = promise { block() }
Upvotes: 5