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

fla*_*ian 47 scala 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] {
       }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*ari 153

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

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

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


厚脸皮编辑:

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

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

  • 啊那个小小的'失败'投影是诀窍,我曾经试过`whenReady`没有成功,直到你指出这一点,谢谢.我认为它的表达力稍差,但如果你出于谨慎,这也会起作用:`f.failed.futureValue shouldBe [SomeExceptionType]`. (8认同)

eas*_*sel 26

这也被隐藏在评论中,但是Scalatest的FutureValues mixin让你满意.

只是用 f.failed.futureValue shouldBe an[TApplicationException]


som*_*ytt 14

注意:留下这个答案是因为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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

特定

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
  }
}
Run Code Online (Sandbox Code Playgroud)

换一种说法,

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
      }
    } 
  } 
} 
Run Code Online (Sandbox Code Playgroud)

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

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

用法

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))
  }
}
Run Code Online (Sandbox Code Playgroud)

灵感来自这个不受欢迎的答案.


Bri*_*Low 10

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


pme*_*pme 7

除了Brian Low 的回答之外,我还找到了一个很好的解释recoverToSucceededIf这在所有异步样式中都可用(来自ScalaTest 3):

\n\n

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

\n\n
    \n
  • recoverToSucceededIf用于断言未来结束的异常类型:
  • \n
\n\n
"return UserNotFoundException" when {\n       "the user does not exist" in {\n         recoverToSucceededIf[UserNotFoundException](userService.findUser("1"))\n       }\n     }\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • recoverToExceptionIf当您想要测试某些异常的字段时非常有用:
  • \n
\n\n
"return UserAlreadyExistsException" when {\n     "adding a user with existing username" in {\n       recoverToExceptionIf[UserAlreadyExistsException] {\n         userService.addUser(user)\n       }.map { ex =>\n         ex.message shouldBe s"User with username: $username already exists!"\n       }\n     }\n   } \n
Run Code Online (Sandbox Code Playgroud)\n\n

查看 Tudor Zgureanu 的整个博客\n\xe2\x80\x94\nScalaTest 3 中的新增功能

\n