scala ScalaTest:在失败的期货中断言异常(非阻塞)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20925352/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:58:59  来源:igfitidea点击:

ScalaTest: Assert exceptions in failed futures (non-blocking)

scalascalatest

提问by flavian

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

Question:How do you assert expected failures in Futures without blocking? The above doesn't work, the exception is thrown before the interceptblock.

问题:如何在不阻塞的情况下断言 Futures 中的预期失败?以上不起作用,在intercept块之前抛出异常。

采纳答案by som-snytt

Note: leaving this answer because the OP found it helpful, but for Scala Futures see the other answer.

注意:留下这个答案是因为 OP 发现它有帮助,但对于 Scala Futures,请参阅另一个答案。

This is a bit boilerplated, but Waiterfrom AsyncAssertions:

这有点样板化,但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
    }
  }
}

given

给予

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

In other words,

换句话说,

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

Or, if you have multiple futures and you want the first non-conforming future to fail the test:

或者,如果您有多个期货,并且希望第一个不合格的期货未能通过测试:

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

with usage

使用

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

Inspired by this unloved answer.

受到这个不受欢迎的答案的启发。

回答by Steven Bakhtiari

I know this is probably a bit late, but ScalaTest provides this feature out of the box (I believe since version 2) by mixing in the ScalaFutures trait, or using it directly in your test functions. Behold!

我知道这可能有点晚了,但是 ScalaTest 通过混合 ScalaFutures 特性或直接在您的测试函数中使用它来提供开箱即用的功能(我相信从第 2 版开始)。看!

test("some test") {
  val f: Future[Something] = someObject.giveMeAFuture
  ScalaFutures.whenReady(f.failed) { e =>
    e shouldBe a [SomeExceptionType]
  }
}

Or you can perform some other assertions in there. Basically, if your future doesn't fail like you expect, the test will fail. If it fails, but throws a different exception, the test will fail. Nice and easy! =]

或者您可以在那里执行一些其他断言。基本上,如果你的未来没有像你期望的那样失败,测试就会失败。如果它失败,但抛出不同的异常,则测试将失败。好,易于!=]



cheeky edit:

厚颜无耻的编辑:

You can also use this method to test anything that returns a future:

您还可以使用此方法来测试返回未来的任何内容:

test("some test") {
  val f: Future[Something] = someObject.giveMeAFuture
  ScalaFutures.whenReady(f) { s =>
    // run assertions against the object returned in the future
  }
}


Most recent edit!

最新编辑!

I just wanted to update this answer with more useful information based on newer versions of Scala test. The various spec traits now all have async support, so instead of extending, say, WordSpec, you would instead extend AsyncWordSpec, and instead of relying on the whenReadycalls as above, you would just map over your futures directly in the test.

我只是想根据更新版本的 Scala 测试用更多有用的信息更新这个答案。各种规范特征现在都具有异步支持,因此不是扩展,比如说,,WordSpec而是扩展AsyncWordSpec,而不是依赖于上述whenReady调用,您只需直接在测试中映射您的期货。

Example:

例子:

class SomeSpec extends Async[*]Spec with Matchers {

...

  test("some test") {
    someObject.funcThatReturnsAFutureOfSomething map { something =>
      // run assertions against the 'something' returned in the future
    }
  }
}

回答by easel

This was buried in a comment as well, but Scalatest's FutureValues mixin has you covered.

这也隐藏在评论中,但 Scalatest 的 FutureValues mixin 已经涵盖了。

Just use f.failed.futureValue shouldBe an[TApplicationException]

只需使用 f.failed.futureValue shouldBe an[TApplicationException]

回答by Brian Low

ScalaTest 3.0 adds async versions of the spec traitslike AsyncFreeSpec:

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

回答by pme

Addition to Brian Low'sanswer, I found a nice explanation for recoverToSucceededIf. This is available in all Async styles(from ScalaTest 3):

除了Brian Low 的回答,我还找到了一个很好的解释recoverToSucceededIf。这适用于所有Async 样式(来自ScalaTest 3):

Failed futures can be tested in two ways: using recoverToSucceededIfor recoverToExceptionIf

可以通过两种方式测试失败的期货:使用recoverToSucceededIfrecoverToExceptionIf

  • recoverToSucceededIfis used for asserting the type of the exception the future ends in:
  • recoverToSucceededIf用于断言未来结束的异常类型:
"return UserNotFoundException" when {
       "the user does not exist" in {
         recoverToSucceededIf[UserNotFoundException](userService.findUser("1"))
       }
     }
  • recoverToExceptionIfis useful when you want to test some of the exception's fields:
  • 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!"
       }
     }
   } 

See the whole blog from Tudor Zgureanu — What's new in ScalaTest 3

查看Tudor Zgureanu的整个博客— ScalaTest 3 中的新功能

回答by Ali

You can also try this Something Simple and Short

你也可以试试这个简单而简短的东西

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