Posted at

scalatestでFutureをテストする方法3通り


問題

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