Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PlayFramework Testing: Uploading File in Fake Request Errors

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.

like image 298
Ophelia Avatar asked Jan 09 '23 13:01

Ophelia


2 Answers

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].

like image 158
Zeimyth Avatar answered Jan 26 '23 12:01

Zeimyth


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.

like image 28
johanandren Avatar answered Jan 26 '23 12:01

johanandren