I want to perform a POST operation using FakeRequest + Specs2.
So far I have been able to write code for making a get request
class ItemsSpec extends PlaySpecification {
"Items controller" should {
"list items" in new WithApplication {
route(FakeRequest(controllers.routes.Items.list())) match {
case Some(response) => status(response) must equalTo (OK) contentAsJson(response) must equalTo (Json.arr())
case None => failure
}
}
}
}
Some of the difficulties which I am facing are
use the reverse looking when doing post on the controller rather than hardcode the operation and path.
Send json body as part of request
parse the results and check if certain properties of the returned object match.
I did some googling and found this
https://www.playframework.com/documentation/2.4.x/ScalaTestingWithSpecs2
in the last example, it seems to be doing a POST. but the path is hardcoded and I don't understand what is essential action.
is there a simple way in which I can write a test case for my Web Service which requires a POST?
Something like this should work
val Some(result) = route(FakeRequest(POST, controllers.routes.MyClassName.myMethod().url)
.withJsonBody(Json.obj("key" -> 1234567)
)
Then check the results as normal. eg.
status(result) must equalTo(OK)
Edit - To read the body, assuming its Json, use this
val js = contentAsJson(result)
val fieldXyz = (js \ "fieldXyz").as[String]
If you're just reading the body as a string, use this
val resultString = contentAsString(result)
Related
I'm trying to read a query parameter in an REST Api I'm building with Akka-http and having trouble unmarshalling it to a domain object. This is my route definition:
override lazy val getManyRoute: Route = (pathEndOrSingleSlash & get & parameters("userId".as[UserId].?)) { maybeUserId =>
complete {
eventService.getEventsBy(maybeUserId)
}
}
Everything compiles fine, so I am assuming the necessary unmarshalling implicits are in scope. Using curl to test the endpoint, I get the following error:
➜ ~ curl "localhost:8080/events?userId=6be0f45e-084e-41db-9e5b-e6bc8d208b54"
The query parameter 'userId' was malformed:
expected whitespace or eof got 'be0f45...' (line 1, column 2)%
It seems like it's reading just the first character ('6') from the UUID, and for some reason is not including the rest of the string in the parameter. However, if I change the Route definition to read a String and manually "unmarshall" it, it works fine. Something like the following:
override lazy val getManyRoute: Route = (pathEndOrSingleSlash & get & parameters("userId".?)) { maybeUserId =>
// ^ no more unmarshalling
complete {
eventService.getEventsBy(maybeUserId.map(functionToUnmarshallStringToUserId))
}
}
Any ideas? Is there something I'm missing in the original route definition?
EDIT:
I have narrowed down the problem using the following change to the route, trying to see exactly what Unmarshaller was being used:
override lazy val getManyRoute: Route = (pathEndOrSingleSlash & get & parameters("userId".?)) { userId =>
complete {
val um = implicitly[Unmarshaller[String, UserId]]
val t = userId map {
um.apply
}
val fuid = t.fold(Future.successful[Option[UserId]](None)) { _.map { a => Some(a) } }
fuid flatMap { eventService.getEventsBy }
}
}
The result was the same, but I got a bit more context in the error:
io.circe.ParsingFailure: expected whitespace or eof got 'be0f45...' (line 1, column 2)
The problem is that my chain of implicits has eventually led to circe being responsible for unmarshalling the query parameter. Because circe is a json library, it expects strings to be in quotes, and so the following curl request works as expected:
curl "localhost:8080/events/?userId=%226be0f45e-084e-41db-9e5b-e6bc8d208b54%22"
# note the URL-escaped quotes (%22) around the id
So now the problem seems to be that I need multiple unmarshallers for the same class in scope, one to unmarshall directly from plain strings for query parameters, and one to unmarshall from json payloads from request entities. Is there a way to do that using implicits? As suggested in comments, I could use “userId”.as(uidUnmarshaller), but I would prefer to take advantage of implicits if possible.
I am writing unit tests for Play application using Scalamock and Scalatest.
My original code looks like:
// Here ws is an injected WSClient
val req = Json.toJson(someRequestObject)
val resp: Future[WSResponse] = ws.url(remoteURL).post(Json.toJson(req))
In a part I have to mock external calls to a web service, which I am trying to do using scalamock:
ws = stub[WSClient]
wsReq = stub[WSRequest]
wsResp = stub[WSResponse]
ws.url _ when(*) returns wsReq
wsReq.withRequestTimeout _ when(*) returns wsReq
(wsReq.post (_: java.io.File)).when(*) returns Future(wsResp)
I am successfully able to mock post requests using a file, but I cannot mock post requests using JSON.
I tried putting stub function references separately like:
val f: StubFunction1[java.io.File, Future[WSResponse]] = wsReq.post (_: java.io.File)
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)
I get the compile error for second line: Unable to resolve overloaded method post
What am I missing here? Why cannot I mock one overloaded method but not the other one?
play.api.libs.ws.WSRequest has two post methods (https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.ws.WSRequest), taking:
File
T (where T has an implicit bounds on Writeable)
The compiler is failing because you are trying to calling post with a single parameter, which only matches version 1. However, JsValue cannot be substituted with File.
You actually want to call the 2nd version, but this is a curried method that takes two sets of parameters (albeit the 2nd are implicit). Therefore you need to explicitly provide the mock value that you expect for the implicit, i.e.
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)(implicitly[Writeable[JsValue]])
Therefore a working solution would be:
(wsReq.post(_)(_)).when(*) returns Future(wsResp)
Old answer:
WSRequest provides 4 overloads of post method (https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html), taking:
String
JsonNode
InputStream
File
You can mock with a File because it matches overload 4, but JsValue does not match (this is part of the Play JSON model, whereas JsonNode is part of the Jackson JSON model). If you convert to a String or JsonNode, then it will resolve the correct overload and compile.
My best guess is that your WSRequest is actually a play.libs.ws.WSRequest which is part of the Java API, instead you should use play.api.libs.ws.WSRequest which is the Scala API.
The method WSRequest.post exists and BodyWritable[JsValue] is implicitly provided by WSBodyWritables in the Scala API but not in the Java API.
Another cause could be that your JsValue is not a play.api.libs.json.JsValue but something else (e.g. spray.json.JsValue).
I'll quote an example where I have successfully achieved what you are trying to do, the main difference is that I used mock instead of stub.
The important part is:
val ws = mock[WSClient]
val responseBody = "{...}"
...
"availableBooks" should {
"retrieve available books" in {
val expectedBooks = "BTC_DASH ETH_DASH USDT_LTC BNB_LTC".split(" ").map(Book.fromString).map(_.get).toList
val request = mock[WSRequest]
val response = mock[WSResponse]
val json = Json.parse(responseBody)
when(ws.url(anyString)).thenReturn(request)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.get()).thenReturn(Future.successful(response))
whenReady(service.availableBooks()) { books =>
books.size mustEqual expectedBooks.size
books.sortBy(_.string) mustEqual expectedBooks.sortBy(_.string)
}
}
}
An you could see the complete test at: BinanceServiceSpec
I guess it should work fine, if you mock a response that is JsValue.
when(wsReq.post(Json.parse("""{...json request...}"""))).thenReturn(Future(wsResp))
Here Json.parse returns JsValue. Yo should pass the json string that you expect in the request body.
I am developing a web service which accepts JSON data. Sometimes input data comes with attachments like image or some PDF file. In that case this data comes as multi-part data.
I need to create action which accepts both content type. And depending on content type it should be able to parse the json and retrieve the attachment related metadata from json and then download the attachment.
I have two actions which handles things separately
def multiPartAction: Action[MultipartFormData[Array[Byte]]] = Action(multipartFormDataAsBytes)={request =>
...
}
Second action
def handleJSon: Action[JsValue] = Action.async(parse.json) {
request =>
...
}
How do I handle these two actions together in one action?
You can either specify your own body parser that is a combination of two, similarly to how it is done here, or leave the body parser out, sticking to default AnyContent body type. Then:
def action = Action { request =>
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
val multipartBody: Option[MultipartFormData[TemporaryFile] =
body.asMultipartFormData
(jsonBody map getResponseForJson) orElse
(multipartBody map getResponseForAttachment) getOrElse
BadRequest("Unsupported request body")
}
I am currently in the process of writing some Specs2 tests for may Play Framework 2.2.x application which accepts MultipartFormData submissions as part of it's function.
I have successfully written other tests with text and JSON bodies using the following form:
"respond to POST JSON with description field present" in {
running(FakeApplication()) {
val response = route(FakeRequest(POST, "/submission.json").withJsonBody(toJson(Map("content" -> toJson("test-content"), "description" -> toJson("test-description"))))).get
status(response) must equalTo(OK)
contentType(response) must beSome.which(_ == "application/json")
contentAsString(response) must contain(""""description":"test-description"""")
contentAsString(response) must contain(""""content":"test-content"""")
}
}
However, when I use the .withMultipartFormData method I get the following errors:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
val response = route(FakeRequest(PUT,"/submission.json/1/files").withMultipartFormDataBody(data)).get
^
The MultipartFormData test I have been attempting to debug is of the form:
"respond to file PUT form with details not specififed" in {
running(FakeApplication()) {
val basePath:String = Play.application.path.getCanonicalPath();
val data:MultipartFormData[TemporaryFile] = MultipartFormData(Map[String,Seq[String]](),
List(
FilePart("file_upload","",Some("Content-Type: multipart/form-data"),TemporaryFile(new java.io.File(basePath + "/test-data/testUpload.jpg")))
),
List(),
List())
val response = route(FakeRequest(PUT,"/submission.json/1/files").withMultipartFormDataBody(data)).get
status(response) must equalTo(CREATED)
}
}
Looking at the Play Framework documentation for the relevant version of the FakeRequest class I can't see too much to help me trace down the problem: play.api.test.FakeRequest
And in terms of other documentation on the matter it seems the Play Framework website and Google are rather lacking.
I have tried the following alternative means of attempting to test my MultipartFormData code:
Writing a test case for file uploads in Play 2.1 and Scala
Test MultipartFormData in Play 2.0 FakeRequest
How do I test multipart form data requests for file uploads in Play Framework 2.0 using Java? (Translating to Scala code first).
However, I have not had any success with any of these approaches either.
Rather than testing in a FakeApplication which is slow and (in my experience) can be error-prone when tests are running in parallel, I've been unit testing my Multipart form upload handlers like this:
Split out the Play wiring from your actual upload handling in your controller; e.g.:
def handleUpload = Action(parse.multipartFormData) { implicit request =>
doUpload(request)
}
def doUpload(request:Request[MultipartFormData[TemporaryFile]]) = {
...
}
(Where handleUpload is the method in your routes file that handles the POST)
Now you've got an endpoint that's easier to get at, you can mock out your service layer to respond appropriately to good/bad requests, and inject the mock service into your controller under test (I won't show that here, there are a million different ways to do that)
Now mock out a multipart request that will arrive at your doUpload method:
val request= mock[Request[MultipartFormData[TemporaryFile]]]
val tempFile = TemporaryFile("do_upload","spec")
val fileName = "testFile.txt"
val part = FilePart("key: String", fileName, None, tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart](), Seq[MissingFilePart]())
request.body returns multipartBody
And finally, you can call your doUpload method and verify functionality:
val result = controller.doUpload(request)
status(result) must beEqualTo(201)
By testing like this, you can quickly and easily test all the error-handling paths in your Controller (which is probably what you're trying to do after all) without the overhead of needing to start the entire application.
In Play 2.5.x, it is easy to test file upload
val file = new java.io.File("the.file")
val part = FilePart[File](key = "thekey", filename = "the.file", contentType = None, ref = file)
val request = FakeRequest().withBody(
MultipartFormData[File](dataParts = Map.empty, files = Seq(part), badParts = Nil)
)
val response = controller.create().apply(request)
status(response) must beEqualTo(201)
(I've answered in the other thread: PlayFramework Testing: Uploading File in Fake Request Errors)
In short, you need a Writeable[AnyContentAsMultipartFormData], which turns MultipartFormData[TemporaryFile] into Array[Byte], and you can take it from here: http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play
I'm new to the Scala/Play 2.1/Specs2 stack, so please excuse me if this question seems simpleton, but I am having difficulty writing a spec to test the case of "String contains the word 'GET'". I've got a Play 2.1 Action that returns an Access-Control-Allow-Methods header value like
Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS
My Spec has no problem doing straight equality checks on other headers, but I have been unable to figure out how to check the Access-Control-Allow-Methods header for each of GET, PUT, POST, DELETE, and OPTIONS. I've expected something like "must contains("GET") to work but the IDE goes red on this with some:
type mismatch; found : org.specs2.matcher.ContainMatcher[String] required: org.specs2.matcher.Matcher[Option[String]] SessionsSpec.scala /dm2-server/test/admin line 53 Scala Problem
My spec looks like...
"send 200 on OPTIONS request on valid route" in {
running(FakeApplication()) {
var fakeReq = FakeRequest("OPTIONS", "/admin/sessions")
val Some(result) = route(fakeReq)
status(result) must equalTo(OK)
header(ACCESS_CONTROL_ALLOW_ORIGIN, result) must equalTo(Some("*"))
header(ACCESS_CONTROL_ALLOW_HEADERS, result) must equalTo(Some(CONTENT_TYPE))
val expectedMethods = Seq(GET, PUT, POST, DELETE, "OPTIONS")
header(ACCESS_CONTROL_ALLOW_METHODS, result) must containAllOf(expectedMethods)
}
}
How do I express the use case of "does this string contain all of these values" in Specs2?
Here's something similar to what you are trying to accomplish:
"A split option string " should{
"be able to be verified with a containsAllOf matcher" in {
val headerVal = Some("a, b, c, d, e")
val expected = Seq("a", "e")
headerVal must beSome
headerVal.get.split(", ").toSeq must containAllOf(expected)
}
}
The issue was that you were trying to take an Option[String] and use that in a Traversable matcher against a Seq. To make that kind of comparison work, both items need to be Traversable. To get to that point, I first make a check on the Option to make sure it's a Some. I then get it and split it on the comma and turn that into a Seq. Once both pieces are Traversable, you can use that containAllOf matcher.