61
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.

Robin Green
  • 32,079
  • 16
  • 104
  • 187
flavian
  • 28,161
  • 11
  • 65
  • 105

6 Answers6

173

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
    }
  }
}
Steven Bakhtiari
  • 3,227
  • 2
  • 20
  • 24
  • Good approach, +1. The accepted answer is more useful since we are using Twitter Futures internally. – flavian Aug 30 '14 at 20:58
  • @flavian: Why wouldn't this work for both Scala and Twitter futures? – Timo Reimann Feb 10 '15 at 01:54
  • @TimoReimann Because Twitter Futures are first and foremost of a different type. I heard Twitter talking about making them extend scala.conccurrent.Future but to my knowledge that's not yet done. The answer to this question has been included into our util library, publicly available here: https://github.com/websudos/util. – flavian Feb 11 '15 at 01:13
  • Nope, not late! Glad you submitted this answer. The ScalaTest DSL takes some getting used to. – Ram Rajamony Apr 28 '15 at 21:11
  • 9
    Ah that little `failed` projection is the trick, I had tried `whenReady` without success until you pointed that out, thanks. It's slightly less expressive I think, but if you're out for terseness this will also work: `f.failed.futureValue shouldBe a [SomeExceptionType]`. – ches May 05 '15 at 10:12
  • I think this is sufficiently useful that it would be worth creating a new question and adding this immediately as the answer and accepting it – Brian Agnew Jun 26 '15 at 08:29
  • FTR, I don't use ScalaTest and have no memory about my answer. But it's great that 41 ppl needed this answer. – som-snytt Nov 07 '15 at 23:04
  • The ability to extends the timeout may come handy: ScalaFutures.whenReady(f.failed, Timeout(scaled(Span(2, Seconds)))) { e => e shouldBe a [MyOwnException] } – dk1844 Nov 22 '17 at 11:35
  • How do I import shouldBe? – Dalton Sweeney Feb 04 '19 at 20:10
  • I'm not sure the most recent edit works to catch an exception. I have a very simple case with a method that returns a `Future[String]` and it seems that my test fails with an exception (of the expected type) before it gets caught by my call to `assertThrows` inside the map. – rspears69 Aug 29 '22 at 22:56
34

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

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

easel
  • 3,982
  • 26
  • 28
  • This is pretty nice that's true but the reason why I cared about the internals is because the code in question was actually based on Twitter Futures as opposed to Scala futures. – flavian Sep 03 '15 at 20:23
  • Interesting. Seems like a "TwitterFutures" mixin that does the same thing would be a nice add-in to ScalaTest if they don't work with twitter futures. – easel Sep 04 '15 at 01:24
17

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.

Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129
14

ScalaTest 3.0 adds async versions of the spec traits like 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
        }
    }
}
Brian Low
  • 11,605
  • 4
  • 58
  • 63
5

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

Failed futures can be tested in two ways: using recoverToSucceededIf or recoverToExceptionIf

  • recoverToSucceededIf is used for asserting the type of the exception the future ends in:
"return UserNotFoundException" when {
       "the user does not exist" in {
         recoverToSucceededIf[UserNotFoundException](userService.findUser("1"))
       }
     }
  • recoverToExceptionIf is useful when you want to test some of the exception's fields:
"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

pme
  • 14,156
  • 3
  • 52
  • 95
0

You can also try this Something Simple and Short

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