-->

PlayFramework FakeRequest returns 400 error

2019-05-10 01:46发布

问题:

In routes:

POST        /login                  controllers.ApplicationCtrl.login()

In Controller:

  def login = Action(parse.json) { implicit request => {

    val email = (request.body \ "email").as[String]
    val password = (request.body \ "password").as[String]

     Ok(Json.toJson(
       Map("status" -> "OK",
        "message" -> "%s created".format(email))
      ))
}

In tests

 "login" in new WithApplication{

      val request = route( FakeRequest(
        Helpers.POST,
        controllers.routes.ApplicationCtrl.login.url,
        FakeHeaders(Seq(CONTENT_TYPE -> Seq("application/json"))),
        """ {"email" : "bob@mail.com", "password" : "secret"} """
      )).get

      status(request) must equalTo(OK)

    }

When I test using command line:

curl --header "Content-type: application/json" --request POST --data '{"email" : "bob@mail.com", "password" : "secret"}' http://localhost:9000/login

It gets desirable response.

{"status":"OK","message":"bob@mail.com created"} 

But the test returns 400 error.

What's wrong?

(command line test wins by simplicity and understandability)

回答1:

What's happening here is that Play sets the content type of the request according to the type of the body. You're using a string body so that the content type header you're setting is later overridden by text/plain; charset=utf-8.

Because you're explicitly parsing the body as Json the body parser will return a bad request 403 if the content type is not either text/json or application/json.

The best thing to do in your case is to use a Json body, i.e:

"login" in new WithApplication {

  val request = route( FakeRequest(
    POST,
    controllers.portal.routes.Portal.test.url,
    FakeHeaders(Seq.empty),
    play.api.libs.json.Json.obj("email" -> "bob@mail.com", "password" -> "secret")
  )).get

  status(request) must equalTo(OK)
}

Note that you can make that a bit more succinct by letting an alternate FakeRequest constructor infer the method and URL of your action from the call:

val request = route(FakeRequest(controllers.portal.routes.Portal.test)
    .withBody(Json.obj("email" -> "bob@mail.com", "password" -> "secret"))).get

Data types you can use as the body parameter and their content type mapping:

  • JsValue -> application/json
  • NodeSeq -> text/xml
  • String -> text/plain
  • Map[String, Seq[String]] -> application/x-www-form-urlencoded
  • Array[Byte] -> nothing

There's also the option of using the tolerantJson as a body parser to skip checking the content type completely.