how do I catch an exception inside a future success using play to test?
Here is my code:
"A Validity" should {
"be created based on Client's Mobile" in new WithApplication {
val objectId = UUIDs.timeBased()
CValidityServiceModule.checkValidity(true, "MOBILE", clientId, objectId)
val future = genericService.findByObjectIdAndByClientId(objectId, clientId)
future.onComplete {
case Success(s) => {
s match {
case Some(v) => {
v.clientId mustEqual clientId
v.objectId mustEqual objectId
}
case None => assert(false)
}
}
case Failure(t) => {
assert(false, t.getMessage)
}
}
}
Basically if any matcher fail, it trows me an exception, but the test is green.
Any idea?
You need to wait for the Future to complete before testing it. Using the onComplete callback won't work because the test completes before the Future, and the exception is thrown in a different context.
Await.result should do it for you:
import scala.concurrent.Await
import scala.concurrent.duration.Duration
val future = genericService.findByObjectIdAndByClientId(objectId, clientId)
val s = Await.result(future, Duration.Inf)
s match {
case Some(v) => {
v.clientId mustEqual clientId
v.objectId mustEqual objectId
}
case None => assert(false)
}
Related
Considering a sequence of futures each returning Either[Status, Resp].
How would you propagate error status codes through a for comprehension which is using Future and not Either?
The code bellow does not work, since the parsing exception is not caught by .recover of the last future
The use case is Scala Play ActionRefiners which returns Future[Either[Status, TRequest[A]]].
def parseId(id: String):Future[Int] = {
Future.successful(Integer.parseInt(id))
}
def getItem(id: Int)(implicit ec: ExecutionContext): Future[Either[Status, String]] =
Future(Some("dummy res from db " + id)).transformWith {
case Success(opt) => opt match {
case Some(item) => Future.successful(Right(item))
case _ => Future.successful(Left(NotFound))
}
case Failure(_) => Future.successful(Left(InternalServerError))
}
(for {
id <- parseId("bad request")
resp <- getItem(id)
} yield resp).recover {
case _:NumberFormatException => Left(BadRequest)
}
I could move the .recover to parseId, but this makes the for comprehension very ugly - having to treat the Either[Status, id] in the middle
def parseId(id: String):Future[Either[Status, Int]] = {
Future.successful(Right(Integer.parseInt(id))).recover {
case _:NumberFormatException => Left(BadRequest)
}
}
Your exception is not caught because you are not throwing it inside the Future: Future.successful is immediately satisfied with the result of the expression you give it, if it throws an exception, it is executed on the current thread.
Try removing the .successful: Future(id.toInt) will do what you want.
Also, I would recommend to get rid of all the Eithers: these are highly overrated/overused, especially in the context of Future (that already wrap their result into Try anyhow), and just make the code more complicated and less readable without offering much benefit.
case class FailureReason(status: Status)
extends Exception(status.toString)
def notFound() = throw FailureReason(NotFound)
def internalError() = throw FailureReason(InternalError)
def badRequest() = throw FailureReason(BadRequest)
def parseId(id: String):Future[Int] = Future(id.toInt)
def getItem(id: Int): Future[String] = Future(Some("dummy"))
.map { _.getOrElse(notFound) }
.recover { _ => internalError }
// this is the same as your for-comprehension, just looking less ugly imo :)
parseId("foo").flatMap(getItem).recover {
case _: NumberFormatException => badRequest()
}
// if you still want `Either` in the end for some reason:
.map(Right.apply[Status, String])
.recover {
case _: NumberFormatException => Left(BadRequest) // no need for the first recover above if you do this
case FailureReason(status) => Left(status)
}
I'm a beginner in Scala.
Please let me know if there is a more concise part in the code below.
To supplement, I'd like to call each Future method synchronously.
◆getUser method:
def getUser: Option[User] = {
Await.ready(
twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF)),
Duration.Inf)
.value
.flatMap(x => Option(x.getOrElse(null)))
}
◆ process method:
def process : Unit =
for {
user <- getUser
} yield {
Await.ready(
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)),
Duration.Inf)
.value
.foreach {
case Success(tweets) => tweets.foreach(tweet => println(s"Delete Successfully!!. $tweet"))
case Failure(exception) => println(s"Failed Delete.... Exception:[$exception]")
}
}
I made some assumptions on user and tweet data types but I would rewrite that to:
def maybeDeleteUser(userName: String, maybeUser: Option[User]): Future[String] =
maybeUser match {
case Some(user) =>
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)).map {
case Failure(exception) => s"Failed Delete.... Exception:[${exception.getMessage}]"
case Success(tweets) => tweets.map(tweet => s"Delete Successfully!!. $tweet").mkString(System.lineSeparator())
}
case _ => Future.successful(s"Failed to find user $userName")
}
def getStatusLogMessage: Future[String] = {
val userName = configService.getString(TWITTER_USERNAME_CONF)
for {
maybeUser <- twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF))
statusLogMessage <- maybeDeleteUser(userName, maybeUser)
} yield statusLogMessage
}
def process: Unit = {
val message = Await.result(getStatusLogMessage, Duration.Inf)
println(message)
}
That way your side effect, i.e. println is isolated and other methods can be unit tested. If you need to block the execution, do it only at the end and use map and flatMap to chain Futures if you need to order the execution of those. Also be careful with Duration.Inf, if you really need to block, then you'd want to have some defined timeout.
How can I return a Left in the middle of a future map chain and then fail the future? I am sending an example to clarify.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object TestApp extends App {
final case class Message(statusCode: Int, data: String)
final case class Success(data: String)
final case class Failure()
val msg = Message(500, "My data")
val future = Future { msg }
def getMessage(future: Future[Message]): Future[Either[Failure, Success]] = {
future.map { msg =>
// Evaluate msg here - if failure, make this feature fail and then return Left(Failure), otherwise, continue in the chain
msg
}.map(_.data).map(data => Right(Success(data)))
}
}
One option is to throw an exception which will fail the Future and then recover to a Left like so
def getMessage(future: Future[Message]): Future[Either[Failure, Success]] =
future
.map(msg => if (msg.statusCode == 500) throw new RuntimeException("boom") else msg)
.map(_.data)
.map(data => Right(Success(data)))
.recover{case ex => Left(Failure())}
Another option is to transform like so
def getMessage(future: Future[Message]): Future[Either[Failure, Success]] = {
future
.map(msg => if (msg.statusCode == 500) throw new RuntimeException("boom") else msg)
.map(_.data)
.transform(v => scala.util.Success {
v.toEither
.map(v => Success(v))
.left.map(e => Failure())
})
}
Warning, you have defined your own Success/Failure which shadows Scala's Success/Failure.
I have an actor with two Messages first responsible for inserting data in mongoDB and the second actor is responsible for inserting data in elasticsearch ,InserInMongo and InserInES namely
there will be a case when mongodb insert operation fails or ES insert operation fails due to some exception and i am doing something like this
try {
val mongoFuture: Future[Boolean] = ask(artGroupPersistenceActor, PersistArtGroupInMongo(artGroup)).mapTo[Boolean]
val esFuture: Future[Boolean] = ask(artGroupPersistenceActor, PersistArtGroupInES(artGroup)).mapTo[Boolean]
val resultMongo = Await.result(mongoFuture, timeout.duration)
log.debug("store: Record Inserted inserted in mongoDB {}", resultMongo)
val resultES = Await.result(esFuture, timeout.duration)
log.debug("store: Record Inserted in ES {}", resultES)
}
catch {
case e: Exception =>
log.error("store:While inserting an artgroup Exception in artGroupPersistenceActor actor", e)
throw e
}
here i want if mongoFuture is failed then i catch its exception and it should proceed with esFuture
or if both future failed i get both exception how can i archive this scenario ?
You can try like this.
for {
x <- Future.successful {
try {
code here...
} catch {
case _: Exception =>
println("Error Inserting In Mongo ")
false
}
}
y <- Future.successful {
try {
code here...
// throw new IllegalStateException("Exception thrown")
} catch {
case _: IllegalStateException =>
println("Error Inserting In ES ")
false
}
}
} yield(x, y)
Now you can manipulate if error occurred while doing the process.. Good luck.
You can go with recover, which will handle any matching throwable that the original Future contains:
val mongoFuture = ask(artGroupPersistenceActor, PersistArtGroupInMongo(artGroup))
.mapTo[Boolean]
.recover {
case e =>
println("error in mongo: " + e)
false
}
val esFuture = ask(artGroupPersistenceActor, PersistArtGroupInES(artGroup))
.mapTo[Boolean]
.recover {
case e =>
println("error in ES: " + e)
false
}
val f = Future.sequence(List(mongoFuture, esFuture))
val result: Seq[Boolean] = Await.result(f, Duration.Inf)
println(result)
If you just want to log the exceptions, just do this with each one:
mongoFuture.failed.foreach {ex => logger.error("Mongo error", ex)}
it better to use map and recover instead of Await.result
import akka.pattern.ask
import scala.concurrent.duration._
implicit val timeout : akka.util.Timeout = 20.seconds
for{
mongoResult <- (artGroupPersistenceActor ? PersistArtGroupInMongo(artGroup)).mapTo[Boolean]
.recover{
case _ =>
log.error("mongodb error")
false
}
elasticResult <- (artGroupPersistenceActor ? PersistArtGroupInES(artGroup)).mapTo[Boolean]
.recover{
case _ =>
log.error("elastic error")
false
}
} yield {
(mongoResult, elasticResult)
}
When I run the code below it terminates and nothing happens, is there a way to catch the exception in future2.map ?
object TestFutures1 {
val future1 = Future {
throw new Exception("error")
}
}
object TestFutures2 extends App {
val future2 = TestFutures1.future1
future2.map { result => println(result) }
Thread.sleep(5000)
}
Generally speaking, there are two options (with some sub-options) to handle future exceptions.
You can add a callback that will be called when the future completes with an exception:
future2
.map { result => println(result) }
.onFailure { exc =>
exc.printStackTrace
}
1a. .onFailure is only used for side effects, you cannot use, for example, to handle the exception. If you want to handle in and rescue the future, you can use .recover or .recoverWith (the only different between the two is that the latter takes a function that returns a Future, while the former deals with the actual result, same idea as map vs. flatMap:
future2
.map(println)
.recover { case e =>
e.printStackTrace
"recovered"
}.map(println)
The other option is to wait for the future to complete, and access it's result:
try {
// Instead of Thread.sleep - you don't need that:
Await.result(future2, 2 seconds)
} catch { case exc =>
exc.printStackTrace
}
Except #Dima's answer, also can use onComplete to catch failure, like:
val re = future2.map { result => println(result) }
re onComplete {
case Success(s) => ...
case Failure(e) => e.printStackTrace()
}