如何使用协程对Kotlin-JS代码进行单元测试?

11

我创建了一个多平台 Koltin 项目(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平台上复制类似的功能?

3个回答

5

可能已经有点晚了,但是有一个开放的问题,可以添加在js-tests中使用挂起函数的可能性(在这个函数中会透明地转换为promise)。

解决方法:

可以在通用代码中定义:

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() } 

我认为你的代码中有一个错别字,但是想法很好,谢谢! - egor.zhdan
2
我很难让JS代码工作,那个错别字具体是什么? - Andrew Steinmetz
这个程序运行得很好,但由于所有测试都是同时开始的(我想是这样的?),当你有许多缓慢的测试时,它们会超过两秒的超时时间。有没有办法修改这个问题,使其没有超时问题? - CLOVIS

2

TL;DR

  1. 在JS中,可以使用 GlobalScope.promise { ... }
  2. 但对于大多数用例,最好的选择可能是使用 runTest { ... }(来自kotlinx-coroutines-test),它是跨平台的,并且与runBlocking { ... }GlobalScope.promise { ... }相比具有其他优点。

完整答案

我不确定问题最初发布时情况如何,但现在运行使用suspend函数的测试的标准跨平台方法是使用runTest { ... }(来自kotlinx-coroutines-test)。

请注意,除了在所有平台上运行之外,这还包括一些其他功能,例如跳过delay(具有模拟时间流逝的能力)。

如果出于任何原因(这不是典型情况,但有时可能会发生),实际上希望在测试中运行代码与在生产中运行的方式相同(包括实际的delay),则可以在JVM和Native上使用runBlocking { ... },在JS上使用GlobalScope.promise { ... }。如果选择此选项,则定义一个函数签名可能很方便,该函数在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)

0
我成功地让以下内容工作:
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 {
  ...
}

我尝试了这个方法,通常是可行的!然而我遇到的一个问题是,当一个测试失败时,它会取消整个任务,导致所有后续测试也失败了。但是,在使用 GlobalScope.promise {...} 时不会出现这种情况(参见我的答案)。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接