web-dev-qa-db-ja.com

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] {
       }
      }
    }
  }
}

質問:ブロックせずにFutureで予想される失敗をどのようにアサートしますか?上記は機能しません。interceptブロックの前に例外がスローされます。

46
flavian

注: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))
  }
}

この愛されていない答え に触発されました。

14
som-snytt

これはおそらく少し遅いことを知っていますが、ScalaTestは、ScalaFuturesトレイトを混合するか、テスト関数で直接使用することで、この機能をすぐに使用できます(バージョン2以降)。見よ!

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
  }
}
150

これもコメントに埋もれていますが、ScalatestのFutureValues mixinでカバーできます。

単にf.failed.futureValue shouldBe an[TApplicationException]

25
easel

ScalaTest 3.0は 仕様の特性の非同期バージョン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
        }
    }
}
10
Brian Low

また、このシンプルで短いものを試すことができます

test("some test throwing SQL Exception") {
      val f: Future[Something] = someObject.giveMeAFuture
      recoverToSucceededIf[SQLException](f)
    }
0
Ali

Brian Lowの答えに加えて、recoverToSucceededIfの素晴らしい説明を見つけました。これはすべての非同期スタイルで利用可能です(fromScalaTest 3):

失敗した先物は2つの方法でテストできます: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!"
       }
     }
   } 

Tudor Zgureanu — ScalaTest 3の新機能 からブログ全体を見る

0
pme