>我创建了一个多平台Kotlin项目(JVM&JS),声明了一个预期的类并实现了它:
// Common module:
expect class Request(/* ... */) {
suspend fun loadText(): String
}
// JS implementation:
actual class Request actual constructor(/* ... */) {
actual suspend fun loadText(): String = suspendCoroutine { continuation ->
// ...
}
}
现在我正在尝试使用kotlin.test
进行单元测试,对于 JVM 平台,我只是使用这样的runBlocking
:
@Test
fun sampleTest() {
val req = Request(/* ... */)
runBlocking { assertEquals( /* ... */ , req.loadText()) }
}
如果没有runBlocking
,如何在JS平台上重现类似的功能?
Mb 已经晚了,但是在 js-tests 中添加使用suspend
函数的可能性存在悬而未决的问题(此函数将透明转换为承诺)
解决方法:
可以在通用代码中定义:
expect fun runTest(block: suspend () -> Unit)
在 JVM 中实现的
actual fun runTest(block: suspend () -> Unit) = runBlocking { block() }
在 JS 中
actual fun runTest(block: suspend () -> Unit): dynamic = promise { block() }
TL;博士
- 在JS上可以使用
GlobalScope.promise { ... }
。 - 但对于大多数用例,最好的选择可能是使用
runTest { ... }
(从kotlinx-coroutines-test
),它是跨平台的,并且与runBlocking { ... }
和GlobalScope.promise { ... }
相比还有一些其他好处。
完整答案
我不确定最初发布问题时的情况如何,但现在运行使用suspend
函数的测试的标准跨平台方法是使用runTest { ... }
(从kotlinx-coroutines-test
开始)。
请注意,除了在所有平台上运行之外,这还包括一些其他功能,例如跳过delay
s(具有模拟时间流逝的能力)。
如果出于任何原因(这不是典型的,但有时可能是这种情况),实际上希望在测试中运行代码,因为它在生产中运行(包括实际的delay
),那么runBlocking { ... }
可以在JVM和Native上使用,GlobalScope.promise { ... }
在JS上使用。如果选择此选项,定义一个在 JVM 和 Native 上使用runBlocking
,在 JS 上使用GlobalScope.promise
的函数签名可能很方便,例如:
// 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)
我能够进行以下工作:
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()
这会使用async
在您选择的范围内启动一个协程,然后您可以像承诺一样返回该协程。
然后,您编写如下测试:
@Test
fun myTest() = coTest {
...
}