Scala 中 Futures 的单元测试失败

Unit Testing failures from Futures in Scala

我正在尝试测试我正在编写的脚本中的错误处理。如果异步函数 fetchBar 失败,我将模式匹配失败案例,然后 return 包含失败结果的成功未来。

val fetchedBar = Try(fooClient.fetchBar(params))

fetchedBar match {
  case Success(bar) => foobar(bar)
  case Failure(e) => Future.successful(FooResult(success = false))
}

但是,当我对该流程进行单元测试时,我在测试失败案例时遇到了问题。我已将 fetchBar 存入 return 一个失败的未来,如下所示。

val fetchedBar = Try(Future.failed(new Exception()))

但我注意到 fetchedBar return 是成功而不是失败。为什么会这样,我如何存根 fetchBar 函数来创建失败的 Try?

我认为您稍微混淆了概念 - 但这并非 100% 是您的错。

问题是,Future 在 Scala 中是一个有点非正交的概念——它不仅代表了延迟执行的概念,还代表了失败的概念。

因此,在大多数情况下,将 Future 包装到 Try 中没有多大意义,反之亦然 - 除非有人想明确地将失败的概念与异步的概念分开。

换句话说,以下组合有点奇怪,但仍然有用:

  1. Try[Future[_]] - 未来已经捕捉到失败。但是,如果你有一个(行为不端的)库方法,通常 returns 一个 Future,但可能会在 "synchronous" 路径上抛出:
def futureReciprocal(i: Int): Float = { 
   val reciprocal = 1 / i // Division by zero is intentional
   Future.successful(reciprocal)
}

futureReciprocal(0) // throws
Try(futureReciprocal(0)) // Failure(DivisionByZero(...))

...但这基本上是解决实现不佳的功能的解决方法

  1. Future[Try[_]] - 有时有助于将 "business" 错误(由 Future.success(Failure(...)) 表示)与 "infrastructure" 失败(由 Future.failed(...) 表示)分开。一方面 - 这对于 akka-streams 特别有用,它倾向于将失败的期货视为流的 "fatal"。

在你的情况下,你想要做的是断言未来的结果。实际上,您至少有两个选择。

  1. 阻止直到未来完成并检查结果 - 这通常是通过 scala.concurrent.Await:
// writing this without the compiler, might mix up namespaces a bit
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt

val future = fooClient.fetchBar(...)
val futureResult: Try[_] = Await.result(future, 1.second)
futureResult match { case Success(_) => ??? ; case Failure(exc) => ???; }
  1. 使用一些支持使用 futures 的测试框架 - 例如最大规模:
class YourTest extends FlatSpec with ScalaFutures {
   "fetchBar should return failed future" in {
        val future: Future[XYZ] = fooClient.fetchBar(...)
        // whenReady comes from the ScalaFutures trait
        whenReady(future) { result => result shouldBe XYZ } // asserting on the successful future result
        whenReady(future.failed) { exc => exc shoulBe a[RuntimeException] } // asserting on an exception in the failed future
   }
}