FutureがFailureを返すときのExceptionの型を検査したい場合、どのようにUTを書けばよいかのメモ。
たとえば以下のように3の倍数または5の倍数でExceptionを吐く Foo.scala
をテストしたい。
object Foo { class BarException extends Exception("bar") class BazException extends Exception("baz") def run(i: Int): Future[Unit] = if ((i % 3) == 0) { Future.failed(new BarException()) } else if ((i % 5) == 0) { Future.failed(new BazException()) } else Future.successful(println(s"value: $i")) }
安直にはこのように書くことができる。
class FooSpec extends AsyncFlatSpec with RecoverMethods { "Foo.run" should "success when not multiple of 3" in { val input = 2 Foo.run(input).map(_ => succeed) } "Foo.run" should "[old] fail when multiple of 3" in { val input = 3 Foo.run(input).failed.map{t => assert(t.isInstanceOf[BarException])} } }
別解としてScalaTestの RecoverMethods
トレイトに recoverToSucceededIf[T]
というメソッドが用意されているのでこれを使うのも良い。このメソッドは assertThrows
のFuture版のようなもので、与えたFutureが例外型Tでrecoverできたら Future[Succeeded]
を返却するため、まさに今回の目的に一致する。これにAsyncXXXSpecと組み合わせて使うと実装がややスッキリする。
class FooSpec extends AsyncFlatSpec with RecoverMethods { "Foo.run" should "fail when multiple of 3" in { val input = 3 recoverToSucceededIf[BarException](Foo.run(input)) } "Foo.run" should "fail when multiple of 5" in { val input = 5 recoverToSucceededIf[BazException](Foo.run(input)) } }
Exceptionのフィールドなどを検査したい場合は recoverToExceptionIf[T]
を使う。こちらはFutureが例外型Tでrecoverできたら Future[T]
を返却するため、あとはmapしてassertすれば例外型の中身まで検査可能となる。
class FooSpec extends AsyncFlatSpec with RecoverMethods with Matchers { "Foo.run" should "fail with message \"bar\" when multiple of 3" in { val input = 5 recoverToExceptionIf[BarException](Foo.run(input)).map(_.getMessage shouldBe "bar") } "Foo.run" should "fail with message \"baz\" when multiple of 5" in { val input = 5 recoverToExceptionIf[BazException](Foo.run(input)).map(_.getMessage shouldBe "baz") } }