[2.3 - Scala] Unit testing Actions with JSON bodyparsers: always gettiing "Bad JSON"

39 views
Skip to first unread message

Thomas Toye

unread,
May 28, 2015, 5:05:13 PM5/28/15
to play-fr...@googlegroups.com
I'm currently switching a traditional Play application over to REST and I'm having a hard time unit testing Actions that use a JSON bodyparser.

Here's an example controller (childRepository is provided by DI):

class ApiChildren(childRepository: ChildRepository) extends Controller {
  // ...

  def newChild: Action[Child] = DBAction(parse.json(childReads)) { implicit req =>
    implicit val session = req.dbSession
    childRepository.insert(req.body)
    Created
  }
}


childReads is a Reads[Child] val.

Here's an example test:

@RunWith(classOf[JUnitRunner])
class ChildrenControllerTest extends PlaySpecification with Mockito {

  val exampleChild = Child(/* ... big case class ... */ )
  // there is an implicit Writes[Child] in scope too

  "Updating a child" should {
    // ...

    "Update the child on a correctly formatted request" in new WithApplication() {
      val mockedRepo = mock[ChildRepository]
      doNothing().when(mockedRepo).update(Matchers.any())(Matchers.any())

      val childController = new ApiChildren(mockedRepo)

      val json = Json.toJson(exampleChild)

      // This is the important line vvv
      val result = childController.update(5).apply(FakeRequest().withJsonBody(json).withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")).run

      status(result) must be equalTo OK
      contentType(result).map { res => res must be equalTo "application/json" }

      there was one(mockedRepo).update(Matchers.any())(Matchers.any())
    }
  }

}

I've tried multiple variations on this theme. The Content-Type header is needed and gets recognized, if missing, I get "Expecting text/json or application/json body". Adding the Content-Type header works.

But I always get HTTP 400's in my tests, with a very very generic "Invalid Json" error. Here's the catch though, when running the application and posting the exact JSON I logged from the test, it works just fine. Can anyone shed some light if I'm doing something wrong here?

Marius Soutier

unread,
Jun 1, 2015, 5:39:31 AM6/1/15
to play-fr...@googlegroups.com

Try .withBody() instead of .withJsonBody(), I remember having had a similar problem...

Thomas Toye

unread,
Jun 1, 2015, 5:36:30 PM6/1/15
to play-fr...@googlegroups.com
Hi Marius, I looked at .withBody(), and it indeed solved my problem. I used it like .withBody[Child](exampleChild).withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")

It feels kind of strange to create a FakeRequest[Child], instead I would have expected to create a FakeRequest[JsValue], or perhaps a FakeRequest[String]. I'm still not sure how to create a FakeRequest with a JSON string to pass into a bodyparsing Action, but since I only use JSON that maps to a case class, I'll stick with that for now.

For anyone who runs into this problem in the future, here's a small example:

class MyTestClass {
  // ...
 
  val exampleChild: Child = Child(...)

  "Updating a child" should {


    "Update the child on a correctly formatted request" in new WithApplication() {
      val mockedRepo = mock[ChildRepository]
      doNothing().when(mockedRepo).update(Matchers.any())(Matchers.any())

      val childController = new ApiChildren(mockedRepo)

      val request: FakeRequest[Child] = FakeRequest().withBody[Child](exampleChild).withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")
      val result = childController.update(5).apply(request)

      //val test = contentAsString(result) // useful for debugging
Reply all
Reply to author
Forward
0 new messages