编写测试用例以在Play 2.1和Scala中上传文件

编写测试用例以在Play 2.1和Scala中上传文件

问题描述:

我找到了以下问题/答案:

I found the following question/answer:

在Play 2.0 FakeRequest中测试MultipartFormData

但是Play 2.1似乎发生了变化.我试图像这样修改示例:

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)

如果我尝试用公正的路由替换过时的routeAndCall(并删除结果周围的Option),则会收到编译错误,指出它无法将MultipartFormData [TemporaryFile]的实例写入HTTP响应.

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.

在带有Scala的Play 2.1中设计此测试的正确方法是什么?

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

我不明白为什么要为映射到结果的字节数组获取Interator.这与我使用自定义正文解析器的方式有关吗?我的控制器的定义如下:

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 =>

使用本文中的表单解析器:从中提取文件Play2/Scala内存中的MultipartFormData

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

Play 2.3包含httpmime.jar的较新版本,需要进行一些较小的更正.基于Marcus使用Play的Writeable机制的解决方案,同时保留了Play 2.1解决方案中的一些语法功能,这是我想出的:

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