Trait that facilitates testing with futures.
This trait defines a FutureConcept trait that can be used to implicitly wrap different kinds of futures, thereby providing a uniform testing API for futures. The three ways this trait enables you to test futures are:
- Invoking
isReadyWithin, to assert that a future is ready within a a specified time period. Here's an example:
assert(result.isReadyWithin(100 millis))
- Invoking
futureValue, to obtain a futures result within a specified or implicit time period, like this:
assert(result.futureValue === 7) // Or, if you expect the future to fail: assert(result.failed.futureValue.isInstanceOf[ArithmeticException])
- Passing the future to
whenReady, and performing assertions on the result value passed to the given function, as in:
whenReady(result) { s =>
s should be ("hello")
}
The whenReady construct periodically inspects the passed future, until it is either ready or the configured timeout has been surpassed. If the future becomes ready before the timeout, whenReady passes the future's value to the specified function.
To make whenReady more broadly applicable, the type of future it accepts is a FutureConcept[T], where T is the type of value promised by the future. Passing a future to whenReady requires an implicit conversion from the type of future you wish to pass (the modeled type) to FutureConcept[T]. Subtrait JavaFutures provides an implicit conversion from java.util.concurrent.Future[T] to FutureConcept[T].
For example, the following invocation of whenReady would succeed (not throw an exception):
import org.scalatest._
import Matchers._
import concurrent.Futures._
import java.util.concurrent._
val exec = Executors.newSingleThreadExecutor
val task = new Callable[String] { def call() = { Thread.sleep(50); "hi" } }
whenReady(exec.submit(task)) { s =>
s should be ("hi")
}
However, because the default timeout is 150 milliseconds, the following invocation of whenReady would ultimately produce a TestFailedException:
val task = new Callable[String] { def call() = { Thread.sleep(500); "hi" } }
whenReady(exec.submit(task)) { s =>
s should be ("hi")
}
Assuming the default configuration parameters, a timeout of 150 milliseconds and an interval of 15 milliseconds, were passed implicitly to whenReady, the detail message of the thrown TestFailedException would look like:
The future passed to whenReady was never ready, so whenReady timed out. Queried 95 times, sleeping 10 milliseconds between each query.
== Configuration of whenReady ==
The whenReady methods of this trait can be flexibly configured. The two configuration parameters for whenReady along with their default values and meanings are described in the following table:
| Configuration Parameter | Default Value | Meaning |
|---|---|---|
| timeout | scaled(150 milliseconds) | the maximum amount of time to allow unsuccessful queries before giving up and throwing TestFailedException |
| interval | scaled(15 milliseconds) | the amount of time to sleep between each query |
The default values of both timeout and interval are passed to the scaled method, inherited from ScaledTimeSpans, so that the defaults can be scaled up or down together with other scaled time spans. See the documentation for trait ScaledTimeSpans for more information.
The whenReady methods of trait Futures each take a PatienceConfig object as an implicit parameter. This object provides values for the two configuration parameters. Trait Futures provides an implicit val named defaultPatience with each configuration parameter set to its default value. If you want to set one or more configuration parameters to a different value for all invocations of whenReady in a suite you can override this val (or hide it, for example, if you are importing the members of the Futures companion object rather than mixing in the trait). For example, if you always want the default timeout to be 2 seconds and the default interval to be 5 milliseconds, you can override defaultPatience, like this:
implicit override val defaultPatience = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
Or, hide it by declaring a variable of the same name in whatever scope you want the changed values to be in effect:
implicit val defaultPatience = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
In addition to taking a PatienceConfig object as an implicit parameter, the whenReady methods of trait Futures include overloaded forms that take one or two PatienceConfigParam objects that you can use to override the values provided by the implicit PatienceConfig for a single whenReady invocation. For example, if you want to set timeout to 6 seconds for just one particular whenReady invocation, you can do so like this:
whenReady (exec.submit(task), timeout(Span(6, Seconds))) { s =>
s should be ("hi")
}
This invocation of eventually will use 6000 for timeout and whatever value is specified by the implicitly passed PatienceConfig object for the interval configuration parameter. If you want to set both configuration parameters in this way, just list them separated by commas:
whenReady (exec.submit(task), timeout(Span(6, Seconds)), interval(Span(500, Millis))) { s =>
s should be ("hi")
}
You can also import or mix in the members of SpanSugar if you want a more concise DSL for expressing time spans:
whenReady (exec.submit(task), timeout(6 seconds), interval(500 millis)) { s =>
s should be ("hi")
}
Note: The whenReady construct was in part inspired by the whenDelivered matcher of the BlueEyes project, a lightweight, asynchronous web framework for Scala.
Attributes
- Companion
- object
- Graph
-
- Supertypes
- Known subtypes