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

play2: FakeRequest().withBody(body) automatically converted to Request[AnyContentAsEmpty] in controller

我正在开发一个 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) }
  }
}

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)
  }
}

但这导致了运行时错误:

[error]    None.get (Application.scala:11)
[error] controllers.Application$$anonfun$index$$anonfun$apply.apply(Application.scala:11)
[error] controllers.Application$$anonfun$index$$anonfun$apply.apply(Application.scala:11)

我在控制器中插入println(request.body),发现请求体是AnyContentAsEmpty,意思是fakeJsonfakeRequest中移除。

如何将 JSON 正确附加到 FakeRequest?

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

如有任何帮助,我将不胜感激。

[自行解决]

又苦苦挣扎了几个小时,使用 withJsonBody:

解决了问题
"Application" should {                                                
  val controller = new Application                                    
  val fakeJson = play.api.libs.json.Json.parse("""{ "foo":"bar" }""") 
  val fakeRequest = FakeRequest().withJsonBody(fakeJson)              
  val index = controller.index()(fakeRequest)                         
  status(index) must equalTo(OK)                                      
}

欢迎提出任何其他建议。

如果客户端对您的操作发出 HTTP POST 请求 不是 JSON,request.body.asJson.get 将抛出一个异常。

  1. body.asJson 具有 return 类型 Option[JsValue],如果请求不是 JSON,则它 return 是 None
  2. None 上调用 get 会引发 java.util.NoSuchElementException
  3. 此异常表现为播放 return500 Internal Server Error

您应该将 def index = Action.async ... 替换为使用 JSON body parser 的操作:

import play.api.mvc.BodyParsers.parse

def index = Action.async(parse.json) ...

这实现了一些事情:

  1. 它更多的是自我记录(操作在方法声明中说 "I expect JSON")。
  2. 如果 POST 不是 JSON,Play 将生成 400 Bad Request。这比你的 None.get.
  3. 造成的 500 Internal Server Error 更合适
  4. 它将使 request.body 成为 JsValue 而不是 AnyContent。所以你可以简单地用 request.body 替换 request.body.asJson.get。一般来说,你应该避免调用 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)

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

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)

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

val fakeJson = Json.obj("foo" -> "bar")