I've been struggling several hours and every time I send a FakeRequest, it seems that the FakeApplication receives an empty body. However, if I try to run the play application and I send a normal request to localhost, everything works as expected, I receive the text I sent as a response.
Another test of the test that I have done is sending a custom response (not the same it receives) from the controller, like a string "a".
My test code looks like:
val controller = app.injector.instanceOf(classOf[CreateRecordController])
val js = Json.obj()
val result = controller.apply().apply(
FakeRequest(POST, "/api/v1/record/")
.withHeaders(Headers(CONTENT_TYPE -> "application/json"))
.withJsonBody(js)
)
val content = contentAsJson(result)
info.apply(s"content was $content")
The last statement prints: "content was" and an html page saying: "For request 'POST /api/v1/record/' [Invalid Json: No content to map due to end-of-input
at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1#5fdfe8cf; line: 1, column: 0]]" -> So the content is empty.
My action handler in the controller is:
def apply: Action[JsValue] = Action.async(parse.json) { implicit request =>
Future.successful(BadRequest(request.body).withHeaders("Content-type" -> "application/json"))
}
Am I missing something?
Play version: 2.6.2
Here you go, give this a read first - https://www.playframework.com/documentation/2.6.x/ScalaEssentialAction
Controller
def work = Action.async(parse.json) { implicit request =>
Future.successful(BadRequest(request.body).withHeaders((CONTENT_TYPE, "application/json")))
}
Test
class ApplicationSpec extends PlaySpec with Results with GuiceOneAppPerTest with Injecting {
"Application" should {
"work" in {
implicit lazy val materializer: Materializer = app.materializer
val controller = new Application(inject[ControllerComponents])
val body = Json.obj()
val result = call(controller.work(), FakeRequest(POST, "/work").withHeaders((CONTENT_TYPE, "application/json")).withJsonBody(body))
contentAsJson(result) mustBe body
}
}
}
It seems that if the content is passed to the FakeRequest at construction time it works as expected. What I've seen is that if I pass a JsValue as the body at construction time, the FakeRequest is of type FakeRequest[JsValue] which works fine. But if the method .withBodyAsJson is used, the type becomes FakeRequest[AnyContentAsJson]. It may be a bug.
Related
I try to write a unit test for a small function in a controller using Play/ScalaTest+ Play. The function I want to test looks like this:
def functionToTest(id: String) = Action.async {
implicit request =>
lang
deeperFunction{ implicit context =>
...
}
}
The deeperFunction
def deeperFunction(block: Context => Future[Result])(implicit request: RequestHeader): Future[Result] = {
// returns Future.successful(Found("DummyUrltoRedirect"))
}
}
The deeperFunction is inherited from a trait and I don't want use the real one here because it's a unit test and so I want to use a matcher instead
val deeperMock = mock[Rainmaker]
val contextMock = mock[Context]
val controller = new Controller()(.....) // list of implicit arguments
"Controller" must {
"return something" in {
val request = FakeRequest("GET", "/something")
when(deeperMock.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
val id = "id"
val result = controller.functionToTest(id).apply(request)
status(result) mustBe Ok
}
}
But when I run this, the line "val result = controller.functionToTest(id).apply(request)" still seems to call the real deeperFunction, not the fake one and therefore throws a null matcher at some point.
I also tried to use
when(controller.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
instead, because the deeperFunction is inherited, but with the same result.
I tried to stick to theses instructions
ScalaTest+Play
dzone
but it seems I am still missing some basics/understanding. Thanks in advance.
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 WithApplication:
"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")
.withJsonBody(requestJson)
val result = profileController.addRoomToProfileForTime()(fakeReq).run
val content = contentAsString(result)
println(content)
status(result) must equalTo(OK)
}
However, this test fails with a Bad Request from Play:
<body>
<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]]
</p>
</body>
If I parse the JSON with request.body.asJson the method behaves as expected. It's only using the body parser method above that I get this error.
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.
Another reason can be not setting Content-Length in Postman application. By mistake I had disabled it, and forgot to enable it. .
Play Framework v 2.1 with scala, I am trying to test my ProductController with invalid call (missing parameters), I am suppose to get BadRequest response..
Controller code is return Ok(Json.toJson(some class..)) or BadRequest(Json.toJson(some class..)) incase something went wrong.
I Defined the test class:
class ProductApplicationTests extends PlaySpecification
with Mockito
with ProductServiceComponent
{
lazy val productService = mock[ProductService]
... other things
def app = FakeApplication(
withoutPlugins = Seq("com.typesafe.plugin.RedisPlugin"),
withGlobal = Some(
new GlobalSettings {
override def getControllerInstance[A](clazz: Class[A]) = clazz match {
case c if c.isAssignableFrom(classOf[ProductController]) => new ProductController(ProductApplicationTests.this).asInstanceOf[A]
case _ => super.getControllerInstance(clazz)
}
}
)
)
def mockStuff = {
productService.addProduct(any[AddProductRequest]) returns
DTOResponse(product.id.get)
productService.updateProduct(any[UpdateProductRequest]) returns
DTOResponse(product.id.get)
productService.deleteProduct(any[DeleteProductRequest]) returns
DTOResponse(product.id.get)
}
step(mockStuff)
"Application" should {
"Get product with no tenant Id" in {running(FakeApplication()) {
val req = FakeRequest(method = "POST", uri = routes.ProductController.getProducts("en_US", "1", "1").url,
headers = FakeHeaders(
Seq("Content-type"->Seq("application/json"))
),
body = None
)
val Some(result) = route(req)
status(result) must equalTo(400)
Problem:
I get error:
Cannot write an instance of None.type to HTTP response. Try to define a Writeable[None.type]
I have followed this post: Play 2 - Scala FakeRequest withJsonBody And i dont understand what im doing wrong..
When i send this code as test it works..
"Get product with valid parameters" in new WithApplication(app) {
val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))
result must beSome
status(result.get) must equalTo(OK)
contentType(result.get) must beSome.which(_ == "application/json")
contentAsString(result.get) must contain("products")
contentAsString(result.get) must contain("notFoundIds")
}
Thanks for any comment/answers..
By the way my global.scala looks like:
override def onBadRequest(request: RequestHeader, error: String) = {
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(error)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
override def onError(request: RequestHeader, ex: Throwable) =
{
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(ex.getMessage)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
And if i run Get in RestAPI test client i get:
{"code":100,"message":"Error processing request - Missing parameter: t"}
If you take a look at the source for FakeRequest, you'll notice that you need to set the body to AnyContentAsEmpty. On a sidenote, should that first test method be a POST? Your other examples seem to be GETs.
As for your second issue with running Get in RestAPI tet client, the error message seems pretty clear, you need to provide the parameter t as you did in the previous example val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))
Hye Geeks. I am coding for a live notification module in my project. I am tyring to call WebSocket Action method from a function to pass the notification data over the connection to the client.
Here's my code..
def liveNotification(data: String) = WebSocket.using[JsValue] { implicit request =>
val iteratee = Iteratee.ignore[JsValue]
val enumerator = Enumerator[JsValue](Json.toJson(data))
(iteratee,enumerator)
}
def createNotification(notificationTo: BigInteger, notiParams:Tuple5[String,String,BigInteger,BigInteger,BigInteger]) = {
val retData = NotificationModel.createNotification(notificationTo,notiParams)
val strData = write(retData)
liveNotification(strData)
}
Problem is that the 'liveNotification()' call is simply ignored. Please help me with any suggestions that what i am doing wrong ?
Be sure to invoke it with a Json value, at least an empty object. The parser will only match against something that it recognizes as Json.
I'm trying to write a filter similar to the simple one described in http://www.playframework.com/documentation/2.1.1/ScalaHttpFilters but I need to access the request body. The documentation below states that "when we invoke next, we get back an Iteratee. You could wrap this in an Enumeratee to do some transformations if you wished." I'm trying to figure out how to wrap the Iteratee so I can get the request body as a string within the filter so I can log that as well.
First thing you have to know is when the Filter is invoked, the request body is not parsed yet. That's why it's giving you a RequestHeader. You'll have to find out the type of body, and call the proper body parser accordingly.
You can find a example of body parsing in the CSRF filter (It can lookup for CSRF tokens in the first bytes of the request body).
See: https://github.com/playframework/playframework/blob/master/framework/src/play-filters-helpers/src/main/scala/csrf.scala#L221-L233.
Hope it helps.
I spent some time on this.
I am no means a Scala expert but this works pretty well! :)
object AccessLog extends EssentialFilter {
def apply(nextFilter: EssentialAction) = new EssentialAction {
def apply(requestHeader: RequestHeader) = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
val bytesToString: Enumeratee[ Array[Byte], String ] = Enumeratee.map[Array[Byte]]{ bytes => new String(bytes) }
val consume: Iteratee[String,String] = Iteratee.consume[String]()
val resultBody : Future[String] = result.body |>>> bytesToString &>> consume
resultBody.map {
body =>
Logger.info(s"${requestHeader.method} ${requestHeader.uri}" +
s" took ${requestTime}ms and returned ${result.header.status}")
val jsonBody = Json.parse(body)
Logger.debug(s"Response\nHeader:\n${result.header.headers.toString}\nBody:\n${Json.prettyPrint(jsonBody)}")
}
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}
The end result will print the body as a Json String (pretty printed).
In the controller method that routes to the action, simply call
Map<String, String[]> params = request().queryString();
This will get you a map of parameters, where you can then call
params.get("someParam")[0]
to get the param (if it is a single value). If the param is a list, ignore the indexing andthat will return an array.