Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
514 views
in Technique[技术] by (71.8m points)

testing - Writing a test case for file uploads in Play 2.1 and Scala

I found the following question/answer:

Test MultipartFormData in Play 2.0 FakeRequest

But it seems things have changed in Play 2.1. I've tried adapting the example like so:

"Application" should {

"Upload Photo" in {
  running(FakeApplication()) {
    val data = new MultipartFormData(Map(), List(
        FilePart("qqfile", "message", Some("Content-Type: multipart/form-data"), 
            TemporaryFile(getClass().getResource("/test/photos/DSC03024.JPG").getFile()))
        ), List())
    val Some(result) = routeAndCall(FakeRequest(POST, "/admin/photo/upload", FakeHeaders(), data)) 
    status(result) must equalTo(CREATED)
    headers(result) must contain(LOCATION)
    contentType(result) must beSome("application/json")  

However whenever I attempt to run the request, I get a null-pointer exception:

[error] ! Upload Photo
[error]     NullPointerException: null (PhotoManagementSpec.scala:25)
[error] test.PhotoManagementSpec$$anonfun$1$$anonfun$apply$3$$anonfun$apply$4.apply(PhotoManagementSpec.scala:28)
[error] test.PhotoManagementSpec$$anonfun$1$$anonfun$apply$3$$anonfun$apply$4.apply(PhotoManagementSpec.scala:25)
[error] play.api.test.Helpers$.running(Helpers.scala:40)
[error] test.PhotoManagementSpec$$anonfun$1$$anonfun$apply$3.apply(PhotoManagementSpec.scala:25)
[error] test.PhotoManagementSpec$$anonfun$1$$anonfun$apply$3.apply(PhotoManagementSpec.scala:25)

If I try to replace the deprecated routeAndCall with just route (and remove the Option around result), I get a compile error stating that it can't write an instance of MultipartFormData[TemporaryFile] to the HTTP response.

What's the right way to design this test in Play 2.1 with Scala?


Edit: Tried to modify the code to test just the controller:

"Application" should {

"Upload Photo" in {

   val data = new MultipartFormData(Map(), List(
   FilePart("qqfile", "message", Some("Content-Type: multipart/form-data"), 
    TemporaryFile(getClass().getResource("/test/photos/DSC03024.JPG").getFile()))
), List())

   val result = controllers.Photo.upload()(FakeRequest(POST, "/admin/photo/upload",FakeHeaders(),data))


   status(result) must equalTo(OK)
   contentType(result) must beSome("text/html")
   charset(result) must beSome("utf-8")
   contentAsString(result) must contain("Hello Bob")
  }

But I now get a type error on all the test conditions around the results like so:

[error]  found   : play.api.libs.iteratee.Iteratee[Array[Byte],play.api.mvc.Result]
[error]  required: play.api.mvc.Result

I don't understand why I'm getting an Interator for byte arrays mapped to Results. Could this have something to do with how I'm using a custom body parser? My controller's definition looks like this:

def upload = Action(CustomParsers.multipartFormDataAsBytes) { request =>

  request.body.file("qqfile").map { upload =>

Using the form parser from this post: Pulling files from MultipartFormData in memory in Play2 / Scala

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Play 2.3 includes a newer version of httpmime.jar, requiring some minor corrections. Building on Marcus's solution using Play's Writeable mechanism, while retaining some of the syntactic sugar from my Play 2.1 solution, this is what I've come up with:

import scala.language.implicitConversions

import java.io.{ByteArrayOutputStream, File}

import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.mime.content._
import org.specs2.mutable.Specification

import play.api.http._
import play.api.libs.Files.TemporaryFile
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.{Codec, MultipartFormData}
import play.api.test.Helpers._
import play.api.test.{FakeApplication, FakeRequest}

trait FakeMultipartUpload {
  implicit def writeableOf_multiPartFormData(implicit codec: Codec): Writeable[MultipartFormData[TemporaryFile]] = {
    val builder = MultipartEntityBuilder.create().setBoundary("12345678")

    def transform(multipart: MultipartFormData[TemporaryFile]): Array[Byte] = {
      multipart.dataParts.foreach { part =>
        part._2.foreach { p2 =>
          builder.addPart(part._1, new StringBody(p2, ContentType.create("text/plain", "UTF-8")))
        }
      }
      multipart.files.foreach { file =>
        val part = new FileBody(file.ref.file, ContentType.create(file.contentType.getOrElse("application/octet-stream")), file.filename)
        builder.addPart(file.key, part)
      }

      val outputStream = new ByteArrayOutputStream
      builder.build.writeTo(outputStream)
      outputStream.toByteArray
    }

    new Writeable[MultipartFormData[TemporaryFile]](transform, Some(builder.build.getContentType.getValue))
  }

  /** shortcut for generating a MultipartFormData with one file part which more fields can be added to */
  def fileUpload(key: String, file: File, contentType: String): MultipartFormData[TemporaryFile] = {
    MultipartFormData(
      dataParts = Map(),
      files = Seq(FilePart[TemporaryFile](key, file.getName, Some(contentType), TemporaryFile(file))),
      badParts = Seq(),
      missingFileParts = Seq())
  }

  /** shortcut for a request body containing a single file attachment */
  case class WrappedFakeRequest[A](fr: FakeRequest[A]) {
    def withFileUpload(key: String, file: File, contentType: String) = {
      fr.withBody(fileUpload(key, file, contentType))
    }
  }
  implicit def toWrappedFakeRequest[A](fr: FakeRequest[A]) = WrappedFakeRequest(fr)
}

class MyTest extends Specification with FakeMultipartUpload {
  "uploading" should {
    "be easier than this" in {
      running(FakeApplication()) {
        val uploadFile = new File("/tmp/file.txt")
        val req = FakeRequest(POST, "/upload/path").
          withFileUpload("image", uploadFile, "image/gif")
        val response = route(req).get
        status(response) must equalTo(OK)
      }
    }
  }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...