Testing Timing with MockTimer in Scala

This article is about unit-testing time-depdenent components via the Time object in Twitter's Util Library for Scala. This object offers special time scopes in which the time can be advanced or set.

Twitter build a wonderful library called twitter-util. The library consists of small and handy reusable helpers. In this article, I want to focus on unit-testing time-dependent components via Time. This object offers special scopes in which the time can be advanced or set. To my surprise, I could not find any articles about this useful pattern.

Scenario

Imagine you want to build an API, which is used to trigger asynchronous jobs. For example a Spark batch job dispatcher. To give it a sense of a real world example, we will use the syntax of Finagle services. For testing, we use ScalaTest. Let us assume we want to unit-test the following scenario:

First, we add a job. After 30 seconds, we query the status of that particular job. Our time out logic withing the API sets the status to timed out after 15 seconds. When the job timed out, we expect a gateway timeout HTTP response.

Example

it should "throw a time out an exception when asking for the status" in {
  val mtimer = new MockTimer()
  val service = new JobService()(mtimer)

  Time.withCurrentTimeFrozen { ctrl =>
    mtimer.tick()

    val reqJob = Request(Method.Post, Path("/v1/jobs"))
    reqJob.setContentString("{job: 'billing', outputFile: 'billing-13.csv'}")
    reqJob.setContentType("application/json; charset=utf-8")
    val jobId = Await.result(service(reqJob))

    ctrl.advance(30.seconds)
    mtimer.tick()

    val reqStatus = Request(Method.Get, Path(s"/v1/jobs/$jobId"))
    val response = Await.result(service(reqStatus))
    response.status shouldEqual Status.GatewayTimeout
  }
}

This is by far not a good job dispatcher example, but it will be perfect for the use case demonstration. We assume that the service takes a Timer. The timeout logic uses this timer to set the status of the job.

To use the time scope, we call Time.withCurrentTimeFrozen. We pass a function with a TimeControl parameter. This parameter object has two functions advance(d: Duration) and set(t: Time). Within the context, we tick() the MockTimer, so it is set to the current time. We add the job, the "mocked" start time of that job is saved (for evaluation if it timed out). After, we advance the time by 30 seconds and tick() the MockTimer again. Finally, when we call the API for a job status, we expect an exception to be thrown by the service.

One or two mails a month about the latest technology I'm hacking on.