Search code examples
scalascalatest

Unit testing that code runs without exception in ScalaTest


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.


Solution

  • 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.