I have a method. This method may return Future.failed(.....) or Future.successful(()).
def calculate(x: Int, y: Int): Future[Unit] = {
........
}
Now I need to test this method. What is the best way to assert for the test which verifies Future.successful(())
case .
Scalatest offers a few ways of working with Future
s.
Option 1: isReadyWithin
import org.scalatest.concurrent.ScalaFutures._
import scala.concurrent.duration._
calculate(1, 3).isReadyWithin(1.second) should be(true)
If you wanted to do something with some return value here, you could use whenReady
:
implicit val patienceConfig = PatienceConfig(1.second)
def calculateWithResult(i: Int, j: Int): Future[Int] = ???
whenReady(calculateWithResult(1,3)) { result =>
result should be(4)
}
You need an implicit PatienceConfig
in scope that tells whenReady
when to fail the test because of a timeout. I believe there's a default one in one of the scalatest
libraries but the time period chosen is quite short - something on the order of 10 milliseconds - and can often cause flaky tests.
Option 2: AsyncXSpec
There are Async
varieties of the FlatSpec
, FreeSpec
, FunSpec
, etc. traits. They work much as their synchronous varieties except that any test must now return a value of type Future[Assertion]
. For instance:
class Test extends AsyncFreeSpec {
"An asynchronous method" - {
"should succeed" in {
calculate(1,3).map { _ =>
// just want to make sure the future completes
succeed
}
}
}
}
Again, you can run a test against the result here. Note that this variant means that every test in your test class must return a Future
, so it's not great if you want to mix synchronous and asynchronous tests. I'm also honestly not sure how AsyncXSpec
chooses its timeout value.
Option Don't: Await.result
I would recommend against using Await.result
as it blocks the thread for the duration. To my knowledge, the above two options are designed so asynchronous tests can be easily run in parallel.
Caveats:
You want to be super careful with your timeouts when doing asynchronous testing. Too long, and your tests can end up hanging for ages if something goes wrong. Too short, and your tests will be flaky. And the program may perform differently in different environments, so you might find that a timeout that's perfectly sufficient on your local machine has the tests on the build server failing 5% of the time. Be careful!
I assume you already extend ScalaFutures
in the test class and you have configured the timeout(patienceConfig
) as per your needs. If you have done that, then you could use one of the below approaches:
whenReady(calculate(1,3))(_ => succeed)
or
whenReady(calculate(1,3))(_ shouldBe ())
The first approach is preferable as there cannot be any other value for Unit
, so we don't have to explicitly check for it, unless the code returns null
for some reason.