How to set a timeout for Async suite in Scalatest?

1.4k views Asked by At

Consider the following unit test example:

class MySpec extends AsyncFlatSpec {

  "this" should "fail after some timeout" in {
    val resultFuture: Future[_] = Promise().future
    
    for (result <- resultFuture) yield {
      assert(result == ???) // some assertions
      assert(result == ???) // some assertions
      assert(result == ???) // some assertions
    }
  }
}

The problem

If resultFuture never completes the test suite never finishes either. As I understand it's due to the way how SerialExecutionContext is implemented.

The question

Is there any "nice" way how to setup a timeout for this kind of tests, so that if the future isn't complete the test just fails, instead of blocking the entire test suite for eternity?


UPDATE and clarification

While solutions https://stackoverflow.com/a/65746143/96766 and https://stackoverflow.com/a/65749840/96766 (posted by @matthias-berndt and @tomer-shetah) work for the case of blocked thread, it's not exactly what I'm looking for.

Let me make an important clarification to the question. In my case the future isn't eventually complete, but never complete. For example, when a Future is obtained from the Promise that is never resolved (nobody calls success nor failure on it). In that case the proposed solutions still block infinitely.

Is there a way to work this around for AsyncSpec without resorting to using a real pool-based execution context and Await-ing on the future?

3

There are 3 answers

1
Zvi Mints On

Use eventually from scalatest

  1. extends Eventually
  2. Use the following code to set up timeout and interval for checking
 import scala.concurrent.duration._
 eventually(timeout(1 minutes), interval(5 seconds)) {
    resultFuture.futureValue shouldBe ???
}
0
Tomer Shetah On

You can use the trait TimeLimits. For example, you can have a test class:

class MySpec extends AsyncFlatSpec with TimeLimits {

  "this" should "fail" in {
    failAfter(2.seconds) {
      val resultFuture: Future[_] = Future {
        Thread.sleep(3000)
      }

      assert(true)
    }
  }

  "this" should "pass" in {
    failAfter(2.seconds) {
      val resultFuture: Future[_] = Future {
        Thread.sleep(1000)
      }

      assert(true)
    }
  }
}
0
Matthias Berndt On