play2: FakeRequest().withBody(body) 在控制器中自动转换为 Request[AnyContentAsEmpty]

iTa*_*shi 3 scala playframework playframework-2.4

我正在开发一个 play-2.4 项目,并编写了一个控制器,如下所示:

package controllers

import play.api._
import play.api.mvc._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class Application extends Controller {
  def index = Action.async { implicit request =>
    Future { Ok(request.body.asJson.get) }
  }
}
Run Code Online (Sandbox Code Playgroud)

POST / controllers.Application.indexconf/routes

我通过执行检查这工作正常curl --request POST --header "Content-type: application/json" --data '{"foo":"bar"}' http://localhost:9000/

现在我为这个控制器编写了一个规范:

package controllers

import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._

import play.api.test._
import play.api.test.Helpers._

@RunWith(classOf[JUnitRunner])
class ApplicationSpec extends Specification {
  "Application" should {
    val controller = new Application
    val fakeJson = """{ "foo":"bar" }"""
    val fakeRequest = FakeRequest()
      .withHeaders("Content-type" -> "application/json")
      .withBody(fakeJson)
    val index = controller.index()(fakeRequest).run
    status(index) must equalTo(OK)
  }
}
Run Code Online (Sandbox Code Playgroud)

但这导致了运行时错误:

[error]    None.get (Application.scala:11)
[error] controllers.Application$$anonfun$index$1$$anonfun$apply$1.apply(Application.scala:11)
[error] controllers.Application$$anonfun$index$1$$anonfun$apply$1.apply(Application.scala:11)
Run Code Online (Sandbox Code Playgroud)

我插入println(request.body)控制器,发现请求正文是AnyContentAsEmpty,意思fakeJson是从 中删除fakeRequest

如何正确地将 JSON 附加到 FakeRequest?

*注意:虽然我可以这样写FakeRequest(POST, '/', FakeHeaders(), fakeJson),但我认为这不好,因为控制器规范不应该处理 HTTP 方法或路由。

我将不胜感激任何帮助。

dan*_*xon 5

如果客户端使用非JSON的请求向您的操作发出 HTTP POST ,request.body.asJson.get将引发异常。

  1. body.asJson有返回类型,如果请求不是 JSON,Option[JsValue]则返回 a 。None
  2. 调用getaNone会抛出 a java.util.NoSuchElementException
  3. 此异常表现为 Play 返回一个500 Internal Server Error.

您应该替换为使用 JSON主体解析器的def index = Action.async ...操作:

import play.api.mvc.BodyParsers.parse

def index = Action.async(parse.json) ...
Run Code Online (Sandbox Code Playgroud)

这实现了一些目标:

  1. 它更加自我记录(该操作在方法声明中显示“我期望 JSON”)。
  2. 400 Bad Request如果 POST 不是 JSON,Play 将生成一个。这比500 Internal Server Error你造成的更合适None.get
  3. 它将request.body代替.JsValueAnyContent所以你可以request.body.asJson.get简单地替换为request.body. 一般来说,您应该避免调用,Option.get因为它不安全,并且通常有更好的方法来实现您想要的(在这种情况下,使用适当的正文解析器恰好是更好的方法)。

现在这个测试不再编译,而不是抛出由以下原因引起的异常None.get

val fakeJson = """{ "foo":"bar" }"""
val fakeRequest = FakeRequest()
  .withHeaders("Content-type" -> "application/json")
  .withBody(fakeJson)
val index = controller.index()(fakeRequest)
status(index) must equalTo(OK)
Run Code Online (Sandbox Code Playgroud)

强制您将其替换为答案中的版本:

val fakeJson = play.api.libs.json.Json.parse("""{ "foo":"bar" }""") 
val fakeRequest = FakeRequest().withBody(fakeJson)              
val index = controller.index()(fakeRequest)                         
status(index) must equalTo(OK)
Run Code Online (Sandbox Code Playgroud)

我最后的建议是你用来Json.obj清理你的测试:

val fakeJson = Json.obj("foo" -> "bar")
Run Code Online (Sandbox Code Playgroud)