Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 intercept block.

like image 466
flavian Avatar asked Jan 04 '14 19:01

flavian


People also ask

What is assert function in Scala?

assert is a precondition in Scala that evaluates a boolean expression as true or false. It's generally used to validate the execution of the program during runtime. We can see assert is used commonly in testing the functionality of programs.


3 Answers

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!

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 whenReady calls as above, you would just map over your futures directly in the test.

Example:

class SomeSpec extends Async[*]Spec with Matchers {  ...    test("some test") {     someObject.funcThatReturnsAFutureOfSomething map { something =>       // run assertions against the 'something' returned in the future     }   } } 
like image 117
Steven Bakhtiari Avatar answered Sep 23 '22 03:09

Steven Bakhtiari


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

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

like image 41
easel Avatar answered Sep 23 '22 03:09

easel


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

This is a bit boilerplated, but Waiter from 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.

like image 36
som-snytt Avatar answered Sep 23 '22 03:09

som-snytt