4

I want to test my method, which requires uploading a file. It is initialized like this:

val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
  key = "students", 
  filename = "students", 
  contentType = Some("text/plain"), 
  ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
  dataParts = Map(), 
  files = Seq(part), 
  badParts = Seq(), 
  missingFileParts = Seq())

I pass it into the FakeRequest:

val result = route(
  FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
    .withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
    .withMultipartFormDataBody(formData)
)

But when I run the test I get the following error:

Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]

What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.

johanandren
  • 10,749
  • 1
  • 19
  • 28
Ophelia
  • 509
  • 1
  • 4
  • 21
  • 1
    Would you post the error, please? – Mon Calamari Apr 17 '15 at 14:42
  • crap. copied the wrong line. thanks for noticing – Ophelia Apr 17 '15 at 14:52
  • possible duplicate of [Play Framework Testing using MultipartFormData in a FakeRequest](http://stackoverflow.com/questions/19658766/play-framework-testing-using-multipartformdata-in-a-fakerequest) – Mon Calamari Apr 17 '15 at 14:58
  • 1
    i didnt find it useful, because answer is not about this error and why. The guy who answered just post his another way to test, without explanation at all. I dont want to do unit testing, I need to solve this error! – Ophelia Apr 17 '15 at 15:01
  • possible duplicate of [Serializing Multipart Form requests for testing on Play 2.1](http://stackoverflow.com/questions/15013177/serializing-multipart-form-requests-for-testing-on-play-2-1) – johanandren Apr 17 '15 at 16:58

3 Answers3

5

It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.

There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)

Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:

val request = FakeRequest(POST, 
    "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
        .withHeaders("Authorization" -> ("Session "))
        .withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])

or make your Writeable a Writeable[AnyContentAsMultipartFormData].

Community
  • 1
  • 1
Zeimyth
  • 1,299
  • 12
  • 19
  • Here's the working Writeable for AnyContentAsMultipartFormData AnyContentAsMultipartFormData http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play – Tanin Jul 31 '15 at 02:42
  • fongmun.com domain seems to have lapsed and currently asks visitors to install a sketchy browser extension. Too bad the code wasn't pasted to a GitHub gist or similar. – einnocent Jul 03 '18 at 23:38
3

route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.

The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).

This means you basically have two options:

  1. write your own Writeable that serializes a MultipartFormData into bytes
  2. Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.

IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.

Community
  • 1
  • 1
johanandren
  • 10,749
  • 1
  • 19
  • 28
  • well, thanks for copy pasting these answers, I saw it all. I dont want to go through the unit testing like in accepted answer for that question. – Ophelia Apr 17 '15 at 16:40
  • I'm not sure where you think I copy pasted these options from. I actually spent a good fifteen minutes out of my free time reading play test helper source code to find an answer for you. But you are very welcome, good luck! – johanandren Apr 17 '15 at 16:43
  • http://stackoverflow.com/questions/15013177/serializing-multipart-form-requests-for-testing-on-play-2-1 this one , and the one you mentioned. – Ophelia Apr 17 '15 at 16:51
1

One of the possible solutions is to use wsUrl. For example

"File uploading action" should {

  "upload sent file and result in ID" in {
    val file = Paths.get(getClass.getResource("/1.txt").toURI)
    val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))

    val res = Await.result(action, timeout)

    res.status mustBe OK
    res.body contains "123"
 }
}
kraken
  • 444
  • 6
  • 17