Future
s in Scala are a very powerful construct. Using map
and
flatMap
, just to name a couple, we can specify code that will be
asynchronously executed only when a Future
is completed successfully. In case
of a failure, the code won’t be executed and these function return the same
failed future. This allows us not to be worried about handling failures when
dealing with Future
execution results, and to write truly asynchronous code.
However, there are cases in which map
is not the most appropriate choice.
Let’s say, for example, we wrote a very complex algorithm that gives an answer to the ultimate question of life, the universe and everything.
def answer: Future[Int] = Future.successful(42)
Great! But we’re one step ahead and we knew already the answer to this question. Hence, we just want to test that the answer is the right one, for example using ScalaTest matchers (for simplicity I’m only specifying the assertions here).
We could map
over it:
answer.map { result =>
result shouldBe 42
}
Great! Our test passes.
However, as we’re developers and not fools, we don’t believe a test that we never saw failing (right?), so we write a test that should fail.
answer.map { result =>
result shouldBe -1
}
Ouch! This passes as well. Why?
Code in the map
block will be executed asynchronously and in another thread.
Tests framework such as ScalaTest work on throwing exceptions for test failures,
and unfortunately this map
approach simply doesn’t work.
In order to test Future
values, ScalaTest offers a trait called ScalaFutures
,
which defines an implicit conversion to FutureConcept
which offers a futureValue
method:
answer.futureValue shouldBe 42
This assertion will work, and it will correctly fail when changing from 42 to -1.
futureValue
takes an implicit PatienceConfiguration
which gives instructions on
how long to wait and how often to check for completion, and it either returns the
value of the successfully completed future, or throws the exception the future
has failed with (or fails the test in case the future is not completed within the
given timeout). So, basically, it’s unwrapping the future for us to have a look
at what’s inside.
While the example showed above is related to a specific test framework, the rationale behind it is universal. While composing futures is always a good idea for production code, there are certain situations in which we want to actually wait for a future to be completed before proceeding. Such as in a test.