felixgb felixgb - 28 days ago 10
Scala Question

Invalid Json: No content to map due to end-of-input when using play body parser

I'm trying to test a controller method that attempts to parse JSON sent in the request:

def addRoomToProfileForTime = Action.async(parse.json[AddRoomToProfileForTimeRequest]) { request =>
profileService.addRoomToProfileForTime(request.body.roomId, request.body.profileId, request.body.timeRange).value.map {
case Xor.Right(_) => Ok
case Xor.Left(err) => BadRequest(Json.toJson(err))

This is the case class that represents the request:

final case class AddRoomToProfileForTimeRequest(
roomId: Int,
profileId: Int,
timeRange: TimeRange

implicit val addRoomToProfileForTimeRequestFormat:Format[AddRoomToProfileForTimeRequest] = Json.format

This code works as expected with I make a request like so:

curl -H "Content-Type: application/json" -X POST -d '{"roomId":3,"profileId":1,"timeRange":{"id":1,"fromTime":"2000-01-01T01:01","toTime":"2000-01-01T02:01"}}' http://localhost:9000/api/profiles/addRoomToProfileForTime

But I'm trying to write a test for this method (note that I am using macwire for dependency injection, hence cannot use

"add a room to profile for time" in new TestContext {
val roomId = 1
val profileId = 1
val from = "2000-01-01T01:01"
val to = "2000-01-01T02:01"
val requestJson = Json.obj(
"roomId" -> roomId,
"profileId" -> profileId,
"timeRange" -> Json.obj(
"id" -> 1,
"fromTime" -> from,
"toTime" -> to

implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()

val fakeReq = FakeRequest(Helpers.POST, "api/profiles/addRoomToProfileForTime")
.withHeaders(CONTENT_TYPE -> "application/json")
val result = profileController.addRoomToProfileForTime()(fakeReq).run

val content = contentAsString(result)
status(result) must equalTo(OK)

However, this test fails with a Bad Request from Play:

<h1>Bad Request</h1>

<p id="detail">
For request 'POST api/profiles/addRoomToProfileForTime' [Invalid Json: No content to map due to end-of-input at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1@37d14073; line: 1, column: 0]]


If I parse the JSON with
the method behaves as expected. It's only using the body parser method above that I get this error.

Answer Source

The short answer is: on your FakeRequest in your controller test use the withBody method instead of withJsonBody.

I had this issue as well, and I embarrassingly spent hours on it until I figured it out. The long answer is that FakeRequest's withJsonBody returns a FakeRequest[AnyContentAsJson], and since your controller is expecting a JsValue (not an AnyContentAsJson), when you call apply() on your action it fails to match this apply method, which is the one you want:

def apply(request: Request[A]): Future[Result]

and instead hits this apply method:

def apply(rh: RequestHeader): Accumulator[ByteString, Result]

and thus since you're not then passing any Bytes to the Accumulator, you get the unexpected end-of-input error message you're getting.