ScalaTest:在失败的期货中断言异常(非阻塞)
Posted
技术标签:
【中文标题】ScalaTest:在失败的期货中断言异常(非阻塞)【英文标题】:ScalaTest: Assert exceptions in failed futures (non-blocking) 【发布时间】:2014-01-22 09:41:43 【问题描述】: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】:我知道这可能有点晚了,但是 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
最近的编辑!
我只是想根据更新版本的 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
【讨论】:
好方法,+1。接受的答案更有用,因为我们在内部使用 Twitter Futures。 @flavian:为什么这不适用于 Scala 和 Twitter 期货? @TimoReimann 因为 Twitter 期货首先是一种不同的类型。我听到 Twitter 谈论让它们扩展 scala.conccurrent.Future 但据我所知,这还没有完成。此问题的答案已包含在我们的 util 库中,可在此处公开获取:github.com/websudos/util。 不,不晚!很高兴您提交了这个答案。 ScalaTest DSL 需要一些时间来适应。 啊,那个小failed
投影是诀窍,我试过whenReady
没有成功,直到你指出来,谢谢。我认为它的表现力稍差,但如果你想简洁,这也可以:f.failed.futureValue shouldBe a [SomeExceptionType]
.【参考方案2】:
这也隐藏在评论中,但 Scalatest 的 FutureValues mixin 已经涵盖了您。
只需使用f.failed.futureValue shouldBe an[TApplicationException]
【讨论】:
这确实很好,但我之所以关心内部结构是因为有问题的代码实际上是基于 Twitter 期货而不是 Scala 期货。 有趣。看起来像一个“TwitterFutures”mixin 如果它们不能与 twitter 期货一起使用,那么它会是一个很好的 ScalaTest 插件。【参考方案3】:注意:留下这个答案是因为 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))
灵感来自this unloved answer。
【讨论】:
【参考方案4】:ScalaTest 3.0 添加async versions of the spec traits 和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】:除了Brian Low 的回答,我还为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!"
查看Tudor Zgureanu — What's new in ScalaTest 3的整个博客
【讨论】:
【参考方案6】:你也可以试试这个简单而简短的东西
test("some test throwing SQL Exception")
val f: Future[Something] = someObject.giveMeAFuture
recoverToSucceededIf[SQLException](f)
【讨论】:
以上是关于ScalaTest:在失败的期货中断言异常(非阻塞)的主要内容,如果未能解决你的问题,请参考以下文章