1

TL;DR I have a Task[Unit], in ScalaTest/FlatSpec what is the proper way to test if the task succeeded in a given time frame?

I have an application with a server-client architecture, the interaction between client and server is non blocking. This is implemented by calls on the client returning a future which completes when the server has finished the work. Importantly this future does not return back the result, it is just used to indicate that the server has finished:

val notification: Task[Unit] = myServer.alterState("do this")
Await.result(notification.runAsync, 20.seconds)

What I want to test here is that the server is correctly sending notifications of completion to the client. I'm testing this with FlatSpec from ScalaTest, and to me it seems that the following should be a valid test:

"Server" should "notify client upon completion" in {
    val notification: Task[Unit] = myServer.alterState("do this")
    Await.result(notification.runAsync, 20.seconds)
}

If the server takes longer than 20 seconds to reply, Await.result will throw an exception, which the test will catch, and fail.

Is this the correct way to perform this sort of test in flatspec? All of the matching framework seems to be geared around testing the value of results, and catching expected exceptions, but I don't have a result returned, I just want to test that the future ends successfully.

Andy
  • 3,228
  • 8
  • 40
  • 65

2 Answers2

2

ScalaFutures enable to assert a Future is ready within a a specified time period like so

import org.scalatest._
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

class ServerSpec extends FlatSpec with ScalaFutures {
  "Server" should "notify client upon completion" in {
    val notification: Task[Unit] = myServer.alterState("do this")
    assert(notification.runAsync.isReadyWithin(20 seconds))
  }
}

AsyncFlatSpec allows for idiomatic Scala syntax where we can map over Future like so

import org.scalatest._

class ServerSpec extends AsyncFlatSpec {
  "Server" should "notify client upon completion" in {
    val notification: Task[Unit] = myServer.alterState("do this")
    notification.runAsync.map(_ => succeed)
  }
}

but make sure server is designed to timeout, otherwise test will hang.

FlatSpec with Await could assert explicitly no exception should be thrown like so

import org.scalatest._
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

class ServerSpec extends FlatSpec with Matchers {
  "Server" should "notify client upon completion" in {
    val notification: Task[Unit] = myServer.alterState("do this")
    noException should be thrownBy Await.result(notification.runAsync, 20.seconds)
  }
}

Personally, I would recommend AsyncFlatSpec method.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
0

you can use the intercept method to verify the exception is thrown.

 val notification: Task[Unit] = myServer.alterState("do this")
 notification onComplete {
   case Failure(_) => fail()
   case Success(_) => succeed
 }
 intercept[TimeoutException] { //you can also use Exception instead of TimeoutException
   Await.result(notification.runAsync, 20.seconds)
  }

If your code throws the exception, intercept catches it, and the test succeeds. (You expected it to throw an exception, and it did.)

More details can be found here or here

Dionysis Nt.
  • 955
  • 1
  • 6
  • 16
  • 1
    This is the opposite of what I'm asking. If the code works, my test is expecting no exceptions, so it shouldn't need to intercept one. Basically the test is, did the code run without exceptions, did the task succeed? – Andy May 09 '19 at 14:25
  • Since you are not returning any result inside the future, all you need to know is that the future was executed within 20 seconds right? – Dionysis Nt. May 09 '19 at 14:30
  • Yes, or if the what executed inside the future threw an exception. E.g. if the server encounters an error and cannot complete the task. – Andy May 09 '19 at 14:33
  • I updated my answer so the test would fail if there is a failure during the task execution. You still need the interception to catch cases that take longer than 20 seconds – Dionysis Nt. May 09 '19 at 14:42
  • Wouldn't that pass if the future times out though? If intercept handles the timeout exception, then timeouts are treated as success right? – Andy May 09 '19 at 14:52
  • no. the purpose of the intercept is to make the test fail if the future times out. check the sources I provided – Dionysis Nt. May 10 '19 at 09:58