Scala has nice abstractions for asynchronous code. However, writing tests for that code sometimes results in an ugly, unreadable mess. Fortunately, ScalaTest has built-in support for testing Futures, in addition to utilities for other types of asynchronous testing, such as polling and test-probes.
org.scalatest.concurrent.Futures
ScalaTest has a trait named Futures which defines functions such as whenReady
, and other goodies like a futureValue
method to help your async tests become terser. However, ScalaTest only comes with support for the standard-library Futures. To use them, mixin org.scalatest.concurrent.ScalaFutures
.
If, currently like me, you're using Twitter Futures, then you need to define your own support for them. Luckily, it is quite easy to define support for any Futures library.
Behold a TwitterFutures trait:
import com.twitter.util.{Throw, Return}
import org.scalatest.concurrent.Futures
trait TwitterFutures extends Futures {
import scala.language.implicitConversions
implicit def convertTwitterFuture[T](twitterFuture: com.twitter.util.Future[T]): FutureConcept[T] =
new FutureConcept[T] {
override def eitherValue: Option[Either[Throwable, T]] = {
twitterFuture.poll.map {
case Return(o) => Right(o)
case Throw(e) => Left(e)
}
}
override def isCanceled: Boolean = false
override def isExpired: Boolean = false
}
}
You may also have to define an implicit PatienceConfig
for your tests as the default settings will timeout after 150 milliseconds.
implicit val asyncConfig = PatienceConfig(timeout = scaled(Span(2, Seconds)))
Polling?
Strangely, ScalaTest chooses to poll futures, despite both Scala and Twitter Futures coming with Await
functions that handle timeouts. Using that as a starting point would have seemed more sensible to me. However, I'm not the author of a successful Scala testing library, and I'm sure that author Bill Venners had a reason. However, it is worth noting.