ScalaTest: Исключить ошибки в неудачных фьючерсах (неблокирующих)
import org.scalatest.{ FlatSpec, Matchers, ParallelTestExecution }
import org.scalatest.concurrent.ScalaFutures
import org.apache.thrift.TApplicationException
class Test extends FlatSpec with Matchers with ScalaFutures with ParallelTestExecution {
it should "throw org.apache.thrift.TApplicationException for invalid Ids" in {
val future: Future[Response] = ThriftClient.thriftRequest
whenReady(future) {
res => {
intercept[TApplicationException] {
}
}
}
}
}
Вопрос:. Как вы утверждаете ожидаемые неудачи в Futures без блокировки? Вышеизложенное не работает, исключение выбрано перед блоком intercept
.
Ответы
Ответ 1
Примечание: оставляя этот ответ, потому что OP нашел его полезным, но для Scala Futures см. другой ответ.
Это немного коллимация, но Waiter
от AsyncAssertions
:
import org.scalatest.{ FlatSpec, Matchers, ParallelTestExecution }
import org.scalatest.concurrent.{ ScalaFutures, AsyncAssertions, PatienceConfiguration }
import concurrent.Future
import concurrent.ExecutionContext.Implicits._
import util._
class Test extends FlatSpec with Matchers with ScalaFutures with ParallelTestExecution with AsyncAssertions {
it should "throw for invalid Ids" in {
val f: Future[Int] = new Goof().goof
val w = new Waiter
f onComplete {
case Failure(e) => w(throw e); w.dismiss()
case Success(_) => w.dismiss()
}
intercept[UnsupportedOperationException] {
w.await
}
}
}
дано
import concurrent.Future
import concurrent.ExecutionContext.Implicits._
class Goof {
def goof(delay: Int = 1): Future[Int] = Future {
Thread sleep delay * 1000L
throw new UnsupportedOperationException
}
def goofy(delay: Int = 1): Future[Int] = Future {
Thread sleep delay * 1000L
throw new NullPointerException
}
def foog(delay: Int = 1): Future[Int] = Future {
Thread sleep delay * 1000L
7
}
}
Другими словами,
class Test extends FlatSpec with Matchers with ScalaFutures with ParallelTestExecution with AsyncAssertions {
it should "throw for invalid Ids" in {
val f: Future[Int] = new Goof().goof
import Helper._
f.failing[UnsupportedOperationException]
}
}
object Helper {
implicit class Failing[A](val f: Future[A]) extends Assertions with AsyncAssertions {
def failing[T <: Throwable](implicit m: Manifest[T]) = {
val w = new Waiter
f onComplete {
case Failure(e) => w(throw e); w.dismiss()
case Success(_) => w.dismiss()
}
intercept[T] {
w.await
}
}
}
}
Или, если у вас есть несколько фьючерсов, и вы хотите, чтобы первое несоответствующее будущее терпело неудачу:
trait FailHelper extends Assertions with AsyncAssertions with PatienceConfiguration {
def failingWith[T <: Throwable : Manifest](fs: Future[_]*)(implicit p: PatienceConfig) {
val count = new java.util.concurrent.atomic.AtomicInteger(fs.size)
val w = new Waiter
for (f <- fs) f onComplete {
case Success(i) =>
w(intercept[T](i))
println(s"Bad success $i")
w.dismiss()
case Failure(e: T) =>
println(s"Failed $e OK, count ${count.get}")
w(intercept[T](throw e))
if (count.decrementAndGet == 0) w.dismiss()
case Failure(e) =>
println(s"Failed $e Bad")
w(intercept[T](throw e))
w.dismiss()
}
w.await()(p)
}
}
с использованием
class Test extends FlatSpec with Matchers with ScalaFutures with ParallelTestExecution with FailHelper {
it should "throw for invalid Ids" in {
val sut = new Goof()
import sut._
val patienceConfig = null // shadow the implicit
implicit val p = PatienceConfig(timeout = 10 seconds)
// all should fail this way
//failingWith[UnsupportedOperationException](goof(), goofy(3), foog(5))
//failingWith[UnsupportedOperationException](goof(), foog(5))
failingWith[UnsupportedOperationException](goof(), goof(2), goof(3))
}
}
Вдохновленный этот нелюбимый ответ.
Ответ 2
Я знаю, что это, вероятно, немного поздно, но ScalaTest предоставляет эту функцию из коробки (я полагаю, начиная с версии 2), смешивая черту ScalaFutures или используя ее непосредственно в ваших тестовых функциях. Вот!
test("some test") {
val f: Future[Something] = someObject.giveMeAFuture
ScalaFutures.whenReady(f.failed) { e =>
e shouldBe a [SomeExceptionType]
}
}
Или вы можете выполнить некоторые другие утверждения там. В принципе, если ваше будущее не провалится, как вы ожидаете, тест провалится. Если это не удается, но выдает другое исключение, тест не пройден. Легко и приятно! =]
дерзкое редактирование:
Вы также можете использовать этот метод для тестирования всего, что возвращает будущее:
test("some test") {
val f: Future[Something] = someObject.giveMeAFuture
ScalaFutures.whenReady(f) { s =>
// run assertions against the object returned in the future
}
}
Последнее изменение!
Я просто хотел обновить этот ответ более полезной информацией, основанной на более новых версиях теста Scala. Все различные спецификационные черты теперь имеют асинхронную поддержку, поэтому вместо расширения, скажем, WordSpec
, вы бы вместо этого расширили AsyncWordSpec
, и вместо того, чтобы полагаться на вызовы whenReady
, как указано выше, вы просто отобразили бы свое будущее напрямую в тесте.
Пример:
class SomeSpec extends Async[*]Spec with Matchers {
...
test("some test") {
someObject.funcThatReturnsAFutureOfSomething map { something =>
// run assertions against the 'something' returned in the future
}
}
}
Ответ 3
Это тоже было похоронено в комментарии, но Scalatest FutureValues mixin вы охватили.
Просто используйте f.failed.futureValue shouldBe an[TApplicationException]
Ответ 4
ScalaTest 3.0 добавляет асинхронные версии спецификаций spec, например AsyncFreeSpec
:
import org.scalatest.{AsyncFlatSpec, Matchers}
import scala.concurrent.Future
class ScratchSpec extends AsyncFlatSpec with Matchers {
def thriftRequest = Future { throw new Exception() }
it should "throw exception" in {
recoverToSucceededIf[Exception] {
thriftRequest
}
}
}
Ответ 5
Вы также можете попробовать это что-то простое и короткое
test("some test throwing SQL Exception") {
val f: Future[Something] = someObject.giveMeAFuture
recoverToSucceededIf[SQLException](f)
}
Ответ 6
Помимо ответа Брайана Лоу, я нашел хорошее объяснение для recoverToSucceededIf
. Это доступно во всех стилях Async (из ScalaTest 3):
Неудачные фьючерсы могут быть протестированы двумя способами: с помощью recoverToSucceededIf
или recoverToExceptionIf
recoverToSucceededIf
используется для определения типа исключения, в котором заканчивается будущее:
"return UserNotFoundException" when {
"the user does not exist" in {
recoverToSucceededIf[UserNotFoundException](userService.findUser("1"))
}
}
recoverToExceptionIf
полезен, когда вы хотите проверить некоторые из полей исключений:
"return UserAlreadyExistsException" when {
"adding a user with existing username" in {
recoverToExceptionIf[UserAlreadyExistsException] {
userService.addUser(user)
}.map { ex =>
ex.message shouldBe s"User with username: $username already exists!"
}
}
}
Посмотреть весь блог от Тудора Згуряну
-
Что нового в ScalaTest 3