FakeRequest withFormUrlEncodedBody data is lost

scala

(yun) #1

Hello!

What I want to do : I try to test a action method addbook with FakeRequest like below.

      val request = FakeRequest()
        .withSession("email" -> "admin@admin.co.jp", "name" -> "yun", "id" -> "1")
        .withFormUrlEncodedBody(
          "name" -> "Great Gatsby",
          "price" -> "30",
          "author" -> "Scott",
          "description" -> "Great classic"
        )
        .withCSRFToken

      val result = homeController.addBook(request).run()(materializer)
      flash(result).get("msg") mustBe Some("some msg")
      status(result) must equal(SEE_OTHER)
      redirectLocation(result) mustBe Some("/somelocation")

What is wrong : But the when I bindFromRequest the Form data, I get nothing but form constraint errors.

[warn] c.HomeController - data : 
[warn] c.HomeController - errors : FormError(name,List(error.required),List()), FormError(price,List(error.required),List())

The addbook action definition is like below

  def addBook = isAuthenticatedAsync { (userId, userName, userEmail) =>
    implicit request =>

      logger.warn("data : " + addBookForm.bindFromRequest.data.mkString(", "))
      logger.warn("errors : " + addBookForm.bindFromRequest.errors.mkString(", "))
   ...

And isAuthenticatedAsync

  def isAuthenticatedAsync (f: => (String, String, String) => MessagesRequest[AnyContent] => Future[Result]) = Security.Authenticated(userInfo, onUnauthorized) { user =>
    Action.async(request => f(user._1,user._2,user._3)(request))
  }

When I change isAuthenticatedAsync to just Async method, I can get the form data but I don’t know what I’m missing, why it is not working.

Please tell me what I’m missing?

Have a great day!


(Rich Dougherty) #2

Can you give the code for isAuthenticatedAsync?


(yun) #3

You mean Authenticated right? Because isAuthenticatedAsync is already there.

Authenticated method resides in play.api.mvc.Security object

Just to emphasize, It works as expected using the real request (using the browser) and I can get the body data.

But with FakeRequest() I’m not able to get any body data because its empty

I think the problem could be related with Authenticated method that takes RequestHeader, and then It is converted(?) to the Request type my main action expects.

I tried to trace the flow of code execution but I couldn’t understand why what is happening

  def Authenticated[A](
    userinfo: RequestHeader => Option[A],
    onUnauthorized: RequestHeader => Result
  )(action: A => EssentialAction): EssentialAction = {

    EssentialAction { request =>
      userinfo(request).map { user =>
        action(user)(request)
      }.getOrElse {
        Accumulator.done(onUnauthorized(request))
      }
    }

  }

(Schmitt Christian) #4

Well if you change the above to:

 val request = FakeRequest()
        .withSession("email" -> "admin@admin.co.jp", "name" -> "yun", "id" -> "1")
        .withFormUrlEncodedBody(
          "name" -> "Great Gatsby",
          "price" -> "30",
          "author" -> "Scott",
          "description" -> "Great classic"
        )
        .withCSRFToken

      val result = call(homeController.addBook, request)

It will work.

The reason why it does not is because you basically call homeController.addBook(request).run()(materializer) which will override the request body to Source.empty[ByteString]. So basically calling a EssentialAction with a body directly, means that the FakeRequest body is ignored.

Here is the Source for call:

def call[T](action: EssentialAction, req: Request[T])(implicit w: Writeable[T], mat: Materializer): Future[Result] =
    call(action, req, req.body)

  def call[T](action: EssentialAction, rh: RequestHeader, body: T)(implicit w: Writeable[T], mat: Materializer): Future[Result] = {
    import play.api.http.HeaderNames._
    val bytes = w.transform(body)

    val contentType = rh.headers.get(CONTENT_TYPE).orElse(w.contentType).map(CONTENT_TYPE -> _)
    val contentLength = rh.headers.get(CONTENT_LENGTH).orElse(Some(bytes.length.toString)).map(CONTENT_LENGTH -> _)
    val newHeaders = rh.headers.replace(contentLength.toSeq ++ contentType.toSeq: _*)

    action(rh.withHeaders(newHeaders)).run(Source.single(bytes))
  }

so as you can see call will just rewrite your FakeRequest so that the body is correctly added.

Guess this is a part of the documentation which could be improved:
https://www.playframework.com/documentation/2.6.x/ScalaTestingWithScalaTest#Unit-Testing-EssentialAction

(well the whole test part could be improved I guess)


(yun) #5

I’m very happy with your answer. Thank you. I didn’t know if there was such method that could do that, even if I had seen those methods before, without example, I would not know how to use them.

By the way if you can suggest me any project that has many testing code examples, It would be great!

Have a wonderful day!


(Schmitt Christian) #6

Well sadly I do not know any projects with testing code examples, besides the play examples that are under https://github.com/playframework however testing is extremely application dependent and personal preference dependent (there are multiple test styles and some applications rely more on a database than others, etc…)

You too !