简体   繁体   中英

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.

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

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.

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"
 }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM