I'm working on a play-2.4 project, and wrote a controller like:
package controllers
import play.api._
import play.api.mvc._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
class Application extends Controller {
def index = Action.async { implicit request =>
Future { Ok(request.body.asJson.get) }
}
}
with POST / controllers.Application.index
in
conf/routes
.
I checked this worked fine by executing curl --request POST --header "Content-type: application/json" --data '{"foo":"bar"}' http://localhost:9000/
.
Now I wrote a spec for this controller:
package controllers
import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._
import play.api.test._
import play.api.test.Helpers._
@RunWith(classOf[JUnitRunner])
class ApplicationSpec extends Specification {
"Application" should {
val controller = new Application
val fakeJson = """{ "foo":"bar" }"""
val fakeRequest = FakeRequest()
.withHeaders("Content-type" -> "application/json")
.withBody(fakeJson)
val index = controller.index()(fakeRequest).run
status(index) must equalTo(OK)
}
}
but this resulted in a runtime error:
[error] None.get (Application.scala:11)
[error] controllers.Application$$anonfun$index$1$$anonfun$apply$1.apply(Application.scala:11)
[error] controllers.Application$$anonfun$index$1$$anonfun$apply$1.apply(Application.scala:11)
I inserted println(request.body)
in the controller and found the request body was AnyContentAsEmpty
, meaning fakeJson
was removed from fakeRequest
.
How can I attach a JSON to FakeRequest properly?
*note: Although I can write like FakeRequest(POST, '/', FakeHeaders(), fakeJson)
, but I think this is not good because controller spec should not handle HTTP methods or routes.
I'll be grateful for any help.
If a client makes an HTTP POST to your action with a request that isn't JSON, request.body.asJson.get
will throw an exception.
body.asJson
has return type Option[JsValue]
and it returns a None
if the request wasn't JSON.get
on a None
throws a java.util.NoSuchElementException
.500 Internal Server Error
.You should replace def index = Action.async ...
with an action that uses a JSON body parser instead:
import play.api.mvc.BodyParsers.parse
def index = Action.async(parse.json) ...
This achieves a few things:
400 Bad Request
if the POST wasn't JSON. This is more appropriate than the 500 Internal Server Error
caused by your None.get
.request.body
a JsValue
instead of AnyContent
. So you can replace request.body.asJson.get
with simply request.body
. In general you should avoid calling Option.get
because it's not safe and there's usually a better way to achieve what you want (using the appropriate body parser happens to be that better way in this case).Now this test no longer compiles, as opposed to throwing the exception caused by None.get
:
val fakeJson = """{ "foo":"bar" }"""
val fakeRequest = FakeRequest()
.withHeaders("Content-type" -> "application/json")
.withBody(fakeJson)
val index = controller.index()(fakeRequest)
status(index) must equalTo(OK)
Forcing you to replace it with the version from your answer:
val fakeJson = play.api.libs.json.Json.parse("""{ "foo":"bar" }""")
val fakeRequest = FakeRequest().withBody(fakeJson)
val index = controller.index()(fakeRequest)
status(index) must equalTo(OK)
My final suggestion is that you use Json.obj
to clean up your test:
val fakeJson = Json.obj("foo" -> "bar")