asynchronous, scala

Testing futures in scala using scala test

Non-blocking external calls (web service) calls often would return a Future which will resolve to some value in the future. Testing such values in Java is pretty sad. Testing futures in java will usually involve performing a get()to resolve a future and then perform the assertions. I am pretty new to scala and was curious if I would have to do the same. Fortunately with Scala this is not the case. I will walk you thru my experience with an example and lets see how we do it in scala. In this post, we will be using scala-test library as our testing library and use FlatSpec type of testing.

Let’s start by defining our class to test. 

import scala.concurrent.{ExecutionContext, Future}
class NetworkCallExample {
implicit val ec: ExecutionContext = ExecutionContext.Implicits.global
private def getResponseFromServer(url: String): Future[Int] = {
Future {
Thread.sleep(5000)
10
}
}
// Dummy method to simulate a network call
def statusOfSomeOtherResponse(url: String): Future[Int] = {
println("Sending out request to some other url")
Future {
Thread.sleep(1000)
200
}
}
//Another dummy call
def lengthOfTheResponse(url: String): Future[Int] = {
println("Sending out request asynchronously to the server")
getResponseFromServer(url)
}
}

The above code is a simple scala class that will simulate a dummy network call by just sleeping for an arbitrary amount time.

lengthOfTheResponse(url: String): Future[Int] is a function that waits for 5 seconds and returns the number 10. 

Similarly, statusOfSomeOtherResponse(url: String): Future[Int]is another function that waits for 1 second and returns the number 200. Let’s see how we write tests for these function. 

import org.scalatest.FlatSpec
import org.scalatest.concurrent.ScalaFutures
class NetworkCallExampleTest extends FlatSpec with ScalaFutures {
it should "verify the future returns when complete" in {
val networkCallExample = new NetworkCallExample
//This will fail with a time out error
whenReady(networkCallExample.lengthOfTheResponse("testurl")) { res =>
assert(res == 10)
}
}
}

The above example looks great but will fail with the following error.

A timeout occurred waiting for a future to complete. Queried 10 times, sleeping 15 milliseconds between each query.

The default behavior of whenReady is to keep polling 10 times by waiting for 15 milliseconds in between to see if the future is resolved. If not, we get the above error. This clearly is not a consistent solution as we are depending on a finite time and polling to figure out the future result. Sure, we can configure the above whenReady method with a higher timeout and retry but there are better ways to test. Let’s see another approach.

class NetworkCallExampleTest extends AsyncFlatSpec {
it should "verify the future returns with for and yield" in {
val networkCallExample = new NetworkCallExample
for{
res <- networkCallExample.lengthOfTheResponse("testurl")
} yield {
assert(res == 10)
}
}
}

The above example will first execute the lengthOfTheResponse("testurl") resolve the future and yield the result Intinto resvariable. We then assert the result. But there is a gotcha we need to keep in mind. It is important that we use the AsyncFlatSpec instead of FlatSpec

We can also do more fluent version with multiple network calls. 

class NetworkCallExampleTest extends AsyncFlatSpec {
it should "verify the future returns with for and yield" in {
val networkCallExample = new NetworkCallExample
// More fluent version
for {
_ <- networkCallExample.lengthOfTheResponse("testurl")
status <- networkCallExample.statusOfSomeOtherResponse("some other url")
} yield {
assert(status == 200)
}
}
}

This way, we can kind of chain the futures in the order that we want to test and perform assertions accordingly. This will ensure that the futures resolve appropriately.

Let me know what you think of this and would certainly like to learn other approach to test futures.

Standard

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.