Отказы модульного тестирования от Futures в Scala - PullRequest
0 голосов
/ 17 января 2020

Я пытаюсь проверить обработку ошибок в сценарии, который я пишу. Если asyn c функция fetchBar завершается с ошибкой, я сопоставляю шаблон с ошибкой, а затем возвращаю успешное будущее, содержащее ошибочный результат.

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

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

Однако при модульном тестировании этого потока возникают проблемы с проверкой ошибки дело. Я поставил флажок fetchBar, чтобы вернуть неудачное будущее, как показано ниже.

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

Но я заметил, что fetchedBar возвращает Успех, а не Отказ. Почему это так и как я могу заглушить функцию fetchBar для создания неудачной попытки?

1 Ответ

1 голос
/ 17 января 2020

Я думаю, что вы немного смешиваете понятия - но это не на 100% ваша вина.

Дело в том, что Future в Scala - это немного неортогональное понятие - так оно и есть, оно представляет не только понятие отложенного выполнения, но и понятие неудачи.

Из-за этого в большинстве случаев нет смысла заключать будущее в попытку, или наоборот - если только хочет явно отделить понятие неудачи от понятия асинхронности.

Другими словами, следующие комбинации являются странными, но все же имеют свое применение:

  1. Try[Future[_]] - будущее уже охватывает неудачи. Однако имеет смысл, если у вас есть (плохое поведение) библиотечный метод, который обычно возвращает Future, но может выдать «синхронный» путь:
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(...))

... но это в основном Обходной путь для исправления плохо реализованной функции

Future[Try[_]] - иногда полезно отделить «бизнес» ошибку (представленную Future.success(Failure(...))) от сбоя «инфраструктуры» (представлен Future.failed(...)). На стороне - это особенно полезно с потоками akka, которые склонны рассматривать неудачные фьючерсы как «фатальные» для потока.

В вашем случае вы хотите заявить о результат будущего. Для этого у вас есть как минимум два варианта.

  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) => ???; }
Используйте некоторую тестовую среду, которая поддерживает работу с фьючерсами, например, scalatest:
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
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...