問題
Futureを使用したテストがかけない。
単純にFutureの値のmapで以下のように書こうとしても、map内のassertの実行前にテストが終了してしまう
HelloFuture.hello().map { s =>
assert(s == "hello!")
}
例としてこちらの単純なクラスのhello()をテストする。
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object HelloFuture {
def hello(): Future[String] = {
Future {
"hello!"
}
}
}
Await.resultを使用
import scala.concurrent.Await
import scala.concurrent.duration._
class HelloFutureWithAwaitSpec extends FlatSpec {
"hello method" should "return string 'hello!'" in {
val result = Await.result(HelloFuture.hello(), 500 millis)
assert(result == "hello!")
}
}
参考:Futures and Promises | Scala Documentation
ScalaFuturesを使用
scalatestに付属している、テスト専用のメソッドであるfutureValueを使用して値を取り出す。
Await.resultよりコードの見通しがよい。
import org.scalatest.FlatSpec
import org.scalatest.concurrent.ScalaFutures
class HelloFutureWithFutureValueSpec extends FlatSpec with ScalaFutures {
"hello method with futureValue" should "return string 'hello!'" in {
assert(HelloFuture.hello().futureValue == "hello!")
}
}
ちなみに、org.scalatest.concurrent.ScalaFuturesのwhenReadyを使用してもテストがかける。
import org.scalatest.FlatSpec
import org.scalatest.concurrent.ScalaFutures
class HelloFutureWithWhenReadySpec extends FlatSpec with ScalaFutures {
"hello method with whenReady" should "return string 'hello!'" in {
whenReady(HelloFuture.hello()) { result =>
assert(result == "hello!")
}
}
}
いずれも、timeout時間と再実行までの間隔intervalを設定できるようなので、Await.resultと同じようにある程度待たないとFutureの処理が終わらない場合も使える。
参考: ScalaTest 3.0.1 - org.scalatest.concurrent.ScalaFutures
Async系のSuiteを使用
scalatest3.xから登場。Future型のmapのブロック内でassertを実行しても、正常に実行可能。
例ではAsyncFlatSpecだが、FlatSpec以外のscalatestが提供している各種suiteのAsync版がある。
import org.scalatest.AsyncFlatSpec
class HelloFutureWithAsyncSpec extends AsyncFlatSpec {
"hello method with async assert" should "return string 'hello!'" in {
HelloFuture.hello() map { result =>
assert(result == "hello!")
}
}
}
ThreadSleepを仕込んで10秒またせたが、終了まできちんと待ってからassertを実行してくれていた。
Future特有のメソッドをわざわざ使わないので実装コードと同じ感覚でかけるのがよい。
参考:ScalaTest