No
2.6.5
Scala
MacOS:
Darwin ***.local 17.0.0 Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64 x86_64
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
scalaVersion := "2.12.3"
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.1"
Bad Request
For request 'GET /' [Invalid Json: No content to map due to end-of-input at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1@161d95c6; line: 1, column: 0]]
Please, find the following repository to reproduce the issue:
https://github.com/Abrasha/playframework-issue-example
Thanks for the report. I'll take a look at this now.
Hi @Abrasha, if you add type annotations into your test you can sort of see what's happening:
val request: Request[AnyContentAsJson] = FakeRequest()
.withJsonBody(Json.toJson(person))
.withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Accumulator[ByteString, Result] = acceptAction.apply(request)
assert(status(response) === CREATED)
The problem is that acceptAction.apply is overloaded - you can either pass a RequestHeader or a Request[JsValue]. Since you're trying to pass a Request[AnyContentAsJson] this matches the RequestHeader version. This returns an Accumulator.
When you call homeController.accept.apply you don't get a result back, you get an Accumulator[ByteString, Result]. When you call status(response) this feeds an empty ByteString to the accumulator, resulting in a parsing error.
The fix is to make a Request[JsValue] which matches the type of acceptAction. If you use a Request[JsValue] it calls the correct accept method, returning a Future[Result]
val request: Request[AnyContentAsJson] = FakeRequest()
.withBody(Json.toJson(person)) // <--- change here
.withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Future[Result] = acceptAction.apply(request) // <-- different type here
assert(status(response) === CREATED)
The alternative fix is to use an AnyContent BodyParser鈥攚hich is the default anyway. See https://www.playframework.com/documentation/2.6.x/ScalaTestingWithScalaTest#Unit-Testing-EssentialAction for an example how this works. You'll need to use request.body.asJson.get inside your action to get the result.
By the way, I realise this is very confusing but unfortunately it's too hard to change Play to make it less confusing. There are several design issues coming together here:
Request extends RequestHeader - this causes issues all over the place, but we can't change it nowAction overloads the apply method for everything instead of having methods with separate names, e.g. accumulator(rh: RequestHeader) and invoke(r: Request[T])AnyContent should only be used rarely but it's the default in our code and docsAction is also an ActionBuilder鈥攁gain using apply methods鈥攁nd making it confusing to understand how Actions are builtI'm going to close this ticket now because it works as designed and we unfortunately can't change the design!
@richdougherty Thank you for your explanation, you save me a lot of time :)
This is one of my pet hates about Play, the overloading semantics are insane and poorly documented.
Most helpful comment
Hi @Abrasha, if you add type annotations into your test you can sort of see what's happening:
The problem is that
acceptAction.applyis overloaded - you can either pass aRequestHeaderor aRequest[JsValue]. Since you're trying to pass aRequest[AnyContentAsJson]this matches theRequestHeaderversion. This returns anAccumulator.When you call
homeController.accept.applyyou don't get a result back, you get anAccumulator[ByteString, Result]. When you callstatus(response)this feeds an emptyByteStringto the accumulator, resulting in a parsing error.The fix is to make a
Request[JsValue]which matches the type ofacceptAction. If you use aRequest[JsValue]it calls the correctacceptmethod, returning aFuture[Result]The alternative fix is to use an
AnyContentBodyParser鈥攚hich is the default anyway. See https://www.playframework.com/documentation/2.6.x/ScalaTestingWithScalaTest#Unit-Testing-EssentialAction for an example how this works. You'll need to userequest.body.asJson.getinside your action to get the result.By the way, I realise this is very confusing but unfortunately it's too hard to change Play to make it less confusing. There are several design issues coming together here:
RequestextendsRequestHeader- this causes issues all over the place, but we can't change it nowActionoverloads theapplymethod for everything instead of having methods with separate names, e.g.accumulator(rh: RequestHeader)andinvoke(r: Request[T])AnyContentshould only be used rarely but it's the default in our code and docsActionis also anActionBuilder鈥攁gain usingapplymethods鈥攁nd making it confusing to understand howActions are builtI'm going to close this ticket now because it works as designed and we unfortunately can't change the design!