FakeRequest withFormUrlEncodedBody data is lost

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!

Can you give the code for isAuthenticatedAsync?

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

  }

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:

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

2 Likes

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!

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 !

1 Like