Play 2.0.3 で BodyParser を含めたアクションのテストを行う

久しぶりの更新です。

Play 2.0(2.0.3) で、ルーターとアクションのテストを行いたい時は普通以下のように書きます。ドキュメントからの引用です。

"respond to the index Action" in {
  val Some(result) = routeAndCall(FakeRequest(GET, "/Bob"))
  
  status(result) must equalTo(OK)
  contentType(result) must beSome("text/html")
  charset(result) must beSome("utf-8")
  contentAsString(result) must contain("Hello Bob")
}

通常はこれで問題ないのですが、自分で定義した BodyParser を利用しているアクションのテストを行いたい場合に問題が生じます。
Helpers.routeAndCall の実装は、2.0.3 では Action#apply を単に呼び出すだけなので、Action#bodyParser は使われないのです。困ります。
それ以外にも、GlobalSettings#onRouteRequest を無視してルーティングを行う問題もあります。困ります。

BodyParser の問題に関しては、このコミットで修正されています。
onRouteRequest の問題に関しては、issueにはあるのですが、2.1.0 ではそもそも routeAndCall が deprecated になるため、修正されることはないでしょう。

まあ何にせよ困る、ので前述のコミットや routeAndCall を deprecated にしたコミットを参考にしつつ、自分で 2.0.3 用のヘルパーメソッドを書いてみました。

import play.api.{Play, Application}
import play.api.mvc.{Action, Result}
import play.api.http.Writeable
import play.api.libs.iteratee.Input
import play.api.test.FakeRequest

object Helpers {
  def route[T](app: Application, rh: FakeRequest[_], body: T)(implicit w: Writeable[T]): Option[Result] = {
    app.global.onRouteRequest(rh).map {
      case action: Action[_] =>
        val rawBody = w.transform(body)
        val parsedBody = action.parser(rh).feed(Input.El(rawBody)).await.get.run.await.get.asInstanceOf[Either[Result, Any]]
        val onSuccess = { (a: Any) =>
          action.asInstanceOf[Action[Any]](rh.copy(body = a))
        }
        val onFailure = (r: Result) => r
        parsedBody.fold(onFailure, onSuccess)
    }
  }

  def route[T: Writeable](rh: FakeRequest[_], body: T): Option[Result] = route(Play.current, rh, body)
}

この code snippet は NYSL ということにしますので、自分で BodyParser 書いちゃうような奇特な人は必要なら適当に使ってください。そういう奇特な人は自分で解決しちゃってそうですが…

以下余談。


さて前述の route の実装は非常にださくて、Action#BODY_CONTENT が疎かにされてしまっている。すごいよくないですね?

しかし、そもそもアクションを呼び出す人間からすれば、そのアクションが内部で body をどのような型の値として扱っているかに関して、興味なんてないわけです。
そんなわけで、本質的にはアクションというのは Request[Array[Byte]] をとって Result を返すものなのだからそうしましょうよ、というのがこのコミットです。
詳細はなんか http://www.twitlonger.com/show/j5908u とか適当に読んでください。書き直すの面倒くさい…

Iteratee 周りのインターフェースもがんがん変わってますし、まだまだ全然安定しているとはいえないですね。anorm もなくなるらしいし。がんばれ〜。