Reputation: 35
I'm trying to test the error handling in a script I'm writing. If the async function fetchBar fails I pattern match the failure case and then return a successful future containing the failed result.
val fetchedBar = Try(fooClient.fetchBar(params))
fetchedBar match {
case Success(bar) => foobar(bar)
case Failure(e) => Future.successful(FooResult(success = false))
}
However when I unit test this flow I'm having trouble testing the failure case. I have stubbed fetchBar to return a failed future as shown below.
val fetchedBar = Try(Future.failed(new Exception()))
But I noticed that fetchedBar returns a Success rather than a Failure. Why is that and how can I stub the fetchBar function to create a failed Try?
Upvotes: 1
Views: 2027
Reputation: 26961
I think you're slightly mixing concepts - but that's not 100% your fault.
The thing is, Future
in Scala is a bit non-orthogonal concept - that it is, it represents not only the notion of delayed execution, but also a notion of failure.
Because of this, in most cases it doesn't make much sense to wrap the Future into the Try, or vice versa - unless one wants to explicitly separate the notion of failure from the notion of asynchrony.
In other words, the following combinations are sort of weird, but still has their use:
Try[Future[_]]
- future already captures failures. However, makes sense if you have a (bad-behaving) library method that normally returns a Future, but may throw on a "synchronous" path:def futureReciprocal(i: Int): Float = {
val reciprocal = 1 / i // Division by zero is intentional
Future.successful(reciprocal)
}
futureReciprocal(0) // throws
Try(futureReciprocal(0)) // Failure(DivisionByZero(...))
... but this is basically a workaround to fix poorly implemented function
Future[Try[_]]
- sometimes useful to separate the "business" error (represented by Future.success(Failure(...))
) from "infrastructure" failure (represented by Future.failed(...)
). On a side - this is especially useful with akka-streams, which tend to treat failed futures as "fatal" to the stream.In your case, what you want to do is to assert on the result of the future. To do so, you have at least two options, actually.
scala.concurrent.Await
:// writing this without the compiler, might mix up namespaces a bit
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt
val future = fooClient.fetchBar(...)
val futureResult: Try[_] = Await.result(future, 1.second)
futureResult match { case Success(_) => ??? ; case Failure(exc) => ???; }
class YourTest extends FlatSpec with ScalaFutures {
"fetchBar should return failed future" in {
val future: Future[XYZ] = fooClient.fetchBar(...)
// whenReady comes from the ScalaFutures trait
whenReady(future) { result => result shouldBe XYZ } // asserting on the successful future result
whenReady(future.failed) { exc => exc shoulBe a[RuntimeException] } // asserting on an exception in the failed future
}
}
Upvotes: 3