如何编写异步 Scala.js 测试(例如使用 ScalaTest)?



我的一些代码是异步的,我想测试这段代码的执行是否导致了正确的状态。我没有可以映射的未来或 JS 承诺的引用——有问题的异步代码存在于我正在使用的 JS 库中,它只是调用setTimeout(setSomeState, 0),这就是为什么我唯一的办法是在短暂延迟(10 毫秒)后异步测试状态。

这是我最好的尝试:

import org.scalatest.{Assertion, AsyncFunSpec, Matchers}    
import scala.concurrent.Promise
import scala.scalajs.js
import scala.scalajs.concurrent.JSExecutionContext
class FooSpec extends AsyncFunSpec with Matchers {
implicit override def executionContext = JSExecutionContext.queue
it("async works") {
val promise = Promise[Assertion]()
js.timers.setTimeout(10) {
promise.success {
println("FOO")
assert(true)
}
}
promise.future
}
}

当断言成功时,这有效 –assert(true).但是,当断言失败时(例如,如果将其替换为assert(false)),测试套件将冻结。SBT 只是停止打印任何内容,并无限期挂起,测试套件永远不会完成。如果出现此类故障FooSpec:确实会打印该行,但不会打印测试的名称("async works"),也不会打印"FOO"字符串。

如果我注释掉executionContext行,我得到"队列为空,而未来未完成,这意味着您可能为您的任务使用了错误的 ExecutionContext,请仔细检查您的未来"错误,该错误在下面的链接之一中有详细说明。

我认为这些链接与这个问题有关:

https://github.com/scalatest/scalatest/issues/910

https://github.com/scalatest/scalatest/issues/1039

但是我想不出一个可行的解决方案。

也许我应该以不同的方式构建Future[Assertion]吗?

我不依赖于 ScalaTest,但从上面一个链接中的评论来看,uTest 似乎有一个类似的问题,除了它倾向于忽略测试而不是停止测试套件。

我只想在短暂的延迟后做出断言,似乎绝对是可能的。关于如何实现这一目标的任何建议将不胜感激。

正如在这个scala.js gitter线程中向我解释的那样,我错误地使用了Promise.success。该方法需要一个值来完成承诺,但assert(false)引发异常,它不会返回类型Assertion的值。

由于在我的代码中,assert(false)是在调用Promise.success之前计算的,因此在承诺有机会完成之前会引发异常。但是,异常会在 setTimeout 的同步回调中引发,因此对 ScalaTest 不可见。然后,ScalaTest 等待永远不会完成的promise.future(因为底层承诺永远不会完成)。

基本上,我的代码等效于这个:

val promise = Promise[Assertion]()
js.timers.setTimeout(10) {
println("FOO")
val successValue = assert(false) // exception thrown here
promise.success(successValue) // this line does not get executed
}
promise.future

相反,我应该使用期望TryPromise.complete.Try.apply在按名称传递模式下接受参数,这意味着只有在调用Try()后才会计算该参数。

所以工作代码看起来像这样:

it("async works") {
val promise = Promise[Assertion]()
js.timers.setTimeout(10) {
promise.complete(Try({
println("FOO")
assert(true)
})
})
promise.future
}

这里真正的答案是:你应该尝试从单元测试设置中去除"异步"部分。

所有处理等待、睡眠等操作都会增加您不希望在单元测试中出现的复杂性。

由于您没有显示您正在使用的生产代码,我只能提出一些建议,如何在一般层面上处理这个主题。

例如:当一个人在Java的ExecutorService之上构建他的线程时,你可以选择相同的线程执行器服务;你的单元测试使用单个线程;许多事情变得容易得多。

长话短说:考虑研究一下在你的解决方案中给你"异步"的"概念";如果有办法避免"真正的异步"部分;但当然不要(!)对你的生产代码进行仅测试的更改。

最新更新