Play Framework Testing using MultipartFormData in a FakeRequest - scala

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

Related

json4s used in scalatra application throws "com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input"

json4s used in scalatra application throws "com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input" when a POST request through a browser.
I have a ScalatraServlet to serve FORM submit from browser. Here is the Servlet.
class PagesController(service: RecordService) extends ScalatraServlet with JacksonJsonSupport {
post("/addRecord") {
contentType = "text/html"
//implicit val formats = DefaultFormats
val jsonPayload = request.body
println(s"payload: $jsonPayload")
val x = parse(request.body)
println(s"parsed: $x")
val record = x.extract[MRecord]
println(s"object: $record")
service.add(Record(0, "Mocked data"))
println(s"added $recordModel")
redirect(URL.LANDING_PAGE_URL)
}
When I run the POST request through cli/rest-client with content-type as appplication/www-form-url-encode, there is no such error and I can confirm from the println statements. However, when a browser submits a form, "com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input"
What is the cause of this exception to occur only when the form is submitted and not when submitted through REST client/cli?
Looks like your code expects that a request body is JSON but a browser form submits param_name1=param_value1&param_name2=param_value2 as a request body. If you have a field named json that contains JSON in your form, probably, you can get a JSON as follows:
post("/addRecord") {
val jsonPayload = params("json")
val x = parse(jsonPayload)
...
}
By the way, the json4s version that is used in Scalatra 2.7.0 is 3.6.7. It would be better to upgrade to this version: https://github.com/scalatra/scalatra/blob/v2.7.0/project/Dependencies.scala#L55

Scala, Hammock - retrieve http response headers and convert JSON to custom object

I have created a simple program which use Hammock(https://github.com/pepegar/hammock) and now I would like to get response from github API with reposne's headers. I created a code like this:
object GitHttpClient extends App {
implicit val decoder = jsonOf[IO, List[GitRepository]]
implicit val interpreter = ApacheInterpreter.instance[IO]
val response = Hammock
.request(Method.GET, uri"https://api.github.com/orgs/github/repos?per_page=3", Map())
.as[List[GitRepository]]
.exec[IO]
.unsafeRunSync()
println(response)
}
case class GitRepository(full_name: String, contributors_url: String)
And it works fine, I got Git data mapped to my object. But now I also want to get headers from response and I cannot do this by simple response.headers. Only when I remove .as[List[GitRepository]] line and have whole HttpResponse I could access headers. Is it possible to get headers without parsing whole HttpResponse?
I solved this problem by using Decoder after received reponse:
val response = Hammock
.request(Method.GET, uri"https://api.github.com/orgs/github/repos?per_page=3", Map())
.exec[IO]
.unsafeRunSync()
println(response.headers("Link") contains ("next"))
println(HammockDecoder[List[GitRepository]].decode(response.entity))

Cannot mock WSRequest.post() using scalamock

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.

Mocking Play WSRequestHolder get method using Scalamock

I'm trying to test the following line of code using ScalaTest and ScalaMock.
val responseFuture = wsClient.url(url).withQueryString(params: _*).get()
wsClient type is THttpClient, which is a wrapper of play.api.libs.ws.WS.
Given that:
val mockHttpClient = mock[THttpClient]
is properly injected into my class under test, the test code is something like this:
val expectedUrl = "some url"
val mockRequestHolder = mock[WSRequestHolder]
inSequence {
(mockHttpClient.url _).expects(expectedUrl).returns(mockRequestHolder)
(mockRequestHolder.withQueryString _).expects(where {
(parameters: Seq[(String, String)]) => {
// assertions on parameters
// ...
true
}
}).returns(mockRequestHolder)
val stubResponse = stub[WSResponse]
val jsonBody = "{}"
(stubResponse.json _).when().returns(Json.parse(jsonBody))
(mockRequestHolder.get _).expects().returns(Future(stubResponse))
}
IntelliJ is highlighting mockRequestHolder.get as an error saying: cannot resolve symbol get. Nevertheless I'm able to run the test but the mock is clearly not working, and I'm getting: java.util.NoSuchElementException: JsError.get.
The mock is working when I try to mock any other method of WSRequestHolder, but not with method get.
Is this a ScalaMock bug or am I doing something wrong?
I don't know if you have solved already the issue, but I have tried to do something similar recently and I kind of got it working with the following code:
val wsClientMock = mock[WSClient]
val wsRequestMock = mock[WSRequest]
val wsResponseMock = mock[WSResponse]
(wsRequestMock.withAuth _).expects(username, password, WSAuthScheme.BASIC).returning(wsRequestMock)
(wsRequestMock.get _).expects().returning(Future[WSResponse](wsResponseMock))
(wsClientMock.url _).expects(bootstrapUrl).returning(wsRequestMock)
(wsResponseMock.status _).expects().returning(200)
"kind of" because I need to mock also the response, otherwise I get results like
ERROR[default-akka.actor.default-dispatcher-4] OneForOneStrategy - Unexpected call: json()
due to the fact that the code calling the WSClient is calling the method .json of WSResponse.
Sorry, I don't know Scala Mock but I suggest you to have a look at MockWS a library which comes with a mocked WS client: play-mockws
With MockWS you define a partial function which returns an Action for a Route. This enables you to precisely configure mocked answers and test your http client code.

Do a POST with Specs2 Fake Request

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)