10

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

在 Play 2.0 FakeRequest 中测试 MultipartFormData

但似乎在 Play 2.1 中情况发生了变化。我试过像这样调整这个例子:

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

但是,每当我尝试运行请求时,都会收到一个空指针异常:

[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(并删除结果周围的选项),我会收到一个编译错误,指出它无法将 MultipartFormData[TemporaryFile] 的实例写入 HTTP 响应。

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


编辑:尝试修改代码以仅测试控制器:

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

但是我现在在结果周围的所有测试条件上都出现类型错误,如下所示:

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

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

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

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

使用这篇文章中的表单解析器:Pulling files from MultipartFormData in memory in Play2 / Scala

4

8 回答 8

15

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

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)
      }
    }
  }
}
于 2014-07-08T00:40:57.420 回答
13

根据各种邮件列表建议,我设法让它与 Play 2.1 一起工作。这是我的做法:

import scala.language.implicitConversions

import java.io.{ ByteArrayOutputStream, File }

import org.apache.http.entity.mime.MultipartEntity
import org.apache.http.entity.mime.content.{ ContentBody, FileBody }
import org.specs2.mutable.Specification

import play.api.http.Writeable
import play.api.test.{ FakeApplication, FakeRequest }
import play.api.test.Helpers._

trait FakeMultipartUpload {
  case class WrappedFakeRequest[A](fr: FakeRequest[A]) {
    def withMultipart(parts: (String, ContentBody)*) = {
      // create a multipart form
      val entity = new MultipartEntity()
      parts.foreach { part =>
        entity.addPart(part._1, part._2)
      }

      // serialize the form
      val outputStream = new ByteArrayOutputStream
      entity.writeTo(outputStream)
      val bytes = outputStream.toByteArray

      // inject the form into our request
      val headerContentType = entity.getContentType.getValue
      fr.withBody(bytes).withHeaders(CONTENT_TYPE -> headerContentType)
    }

    def withFileUpload(fileParam: String, file: File, contentType: String) = {
      withMultipart(fileParam -> new FileBody(file, contentType))
    }
  }

  implicit def toWrappedFakeRequest[A](fr: FakeRequest[A]) = WrappedFakeRequest(fr)

  // override Play's equivalent Writeable so that the content-type header from the FakeRequest is used instead of application/octet-stream  
  implicit val wBytes: Writeable[Array[Byte]] = Writeable(identity, None)
}

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)
      }
    }
  }
}
于 2013-03-02T05:35:05.557 回答
5

我修改了 Alex 的代码以充当可更好地集成到 Play 2.2.2 中的 Writable

package test

import play.api.http._
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.iteratee._
import play.api.libs.Files.TemporaryFile
import play.api.mvc.{Codec, MultipartFormData }
import java.io.{FileInputStream, ByteArrayOutputStream}
import org.apache.commons.io.IOUtils
import org.apache.http.entity.mime.MultipartEntity
import org.apache.http.entity.mime.content._

object MultipartWriteable {

  /**
   * `Writeable` for multipart/form-data.
   *
   */
  implicit def writeableOf_multiPartFormData(implicit codec: Codec): Writeable[MultipartFormData[TemporaryFile]] = {

    val entity = new MultipartEntity()

    def transform(multipart: MultipartFormData[TemporaryFile]):Array[Byte] = {

      multipart.dataParts.foreach { part =>
        part._2.foreach { p2 =>
            entity.addPart(part._1, new StringBody(p2))
        }
      }

      multipart.files.foreach { file =>
        val part = new FileBody(file.ref.file, file.filename,     file.contentType.getOrElse("application/octet-stream"), null)
        entity.addPart(file.key, part)
      }

      val outputStream = new ByteArrayOutputStream
      entity.writeTo(outputStream)
      val bytes = outputStream.toByteArray
      outputStream.close
      bytes
    }

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

这样就可以写出这样的东西:

val filePart:MultipartFormData.FilePart[TemporaryFile] = MultipartFormData.FilePart(...)
val fileParts:Seq[MultipartFormData.FilePart[TemporaryFile]] = Seq(filePart)
val dataParts:Map[String, Seq[String]] = ...
val multipart = new MultipartFormData[TemporaryFile](dataParts, fileParts, List(), List())
val request = FakeRequest(POST, "/url", FakeHeaders(), multipart)

var result = route(request).get
于 2014-06-12T12:50:41.360 回答
2

按照 EEColor 的建议,我得到了以下工作:

"Upload Photo" in {


    val file = scala.io.Source.fromFile(getClass().getResource("/photos/DSC03024.JPG").getFile())(scala.io.Codec.ISO8859).map(_.toByte).toArray

    val data = new MultipartFormData(Map(), List(
    FilePart("qqfile", "DSC03024.JPG", Some("image/jpeg"),
        file)
    ), List())

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

    status(result) must equalTo(CREATED)
    headers(result) must haveKeys(LOCATION)
    contentType(result) must beSome("application/json")      


  }
于 2013-03-03T03:23:43.963 回答
1

这是我的 Writeable[AnyContentAsMultipartFormData] 版本:

import java.io.File

import play.api.http.{HeaderNames, Writeable}
import play.api.libs.Files.TemporaryFile
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.{AnyContentAsMultipartFormData, Codec, MultipartFormData}

object MultipartFormDataWritable {
  val boundary = "--------ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"

  def formatDataParts(data: Map[String, Seq[String]]) = {
    val dataParts = data.flatMap { case (key, values) =>
      values.map { value =>
        val name = s""""$key""""
        s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name\r\n\r\n$value\r\n"
      }
    }.mkString("")
    Codec.utf_8.encode(dataParts)
  }

  def filePartHeader(file: FilePart[TemporaryFile]) = {
    val name = s""""${file.key}""""
    val filename = s""""${file.filename}""""
    val contentType = file.contentType.map { ct =>
      s"${HeaderNames.CONTENT_TYPE}: $ct\r\n"
    }.getOrElse("")
    Codec.utf_8.encode(s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name; filename=$filename\r\n$contentType\r\n")
  }

  val singleton = Writeable[MultipartFormData[TemporaryFile]](
    transform = { form: MultipartFormData[TemporaryFile] =>
      formatDataParts(form.dataParts) ++
        form.files.flatMap { file =>
          val fileBytes = Files.readAllBytes(Paths.get(file.ref.file.getAbsolutePath))
          filePartHeader(file) ++ fileBytes ++ Codec.utf_8.encode("\r\n")
        } ++
        Codec.utf_8.encode(s"--$boundary--")
    },
    contentType = Some(s"multipart/form-data; boundary=$boundary")
  )
}

implicit val anyContentAsMultipartFormWritable: Writeable[AnyContentAsMultipartFormData] = {
  MultipartFormDataWritable.singleton.map(_.mdf)
}

它改编自(并修复了一些错误):https ://github.com/jroper/playframework/blob/multpart-form-data-writeable/framework/src/play/src/main/scala/play/api/http/可写.scala#L108

如果您有兴趣,请在此处查看整个帖子:http: //tech.fongmun.com/post/125479939452/test-multipartformdata-in-play

于 2015-07-31T02:54:40.757 回答
1

对我来说,这个问题的最佳解决方案是Alex Varju one

这是为 Play 2.5 更新的版本:

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

    def transform(multipart: AnyContentAsMultipartFormData): ByteString = {
      multipart.mdf.dataParts.foreach { part =>
        part._2.foreach { p2 =>
          builder.addPart(part._1, new StringBody(p2, ContentType.create("text/plain", "UTF-8")))
        }
      }
      multipart.mdf.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)
      ByteString(outputStream.toByteArray)
    }

    new Writeable(transform, Some(builder.build.getContentType.getValue))
  }
}
于 2016-12-19T17:10:28.530 回答
1

在 Play 2.6.x 中,您可以通过以下方式编写测试用例来测试文件上传 API:

class HDFSControllerTest extends Specification {
  "HDFSController" should {
    "return 200 Status for file Upload" in new WithApplication {

      val tempFile = SingletonTemporaryFileCreator.create("txt","csv")
      tempFile.deleteOnExit()

      val data = new MultipartFormData[TemporaryFile](Map(),
      List(FilePart("metadata", "text1.csv", Some("text/plain"), tempFile)), List())

      val res: Option[Future[Result]] = route(app, FakeRequest(POST, "/api/hdfs").withMultipartFormDataBody(data))
      print(contentAsString(res.get))
      res must beSome.which(status(_) == OK)
   }
  }
}
于 2019-05-20T13:55:46.910 回答
0

使 Alex 的版本与 Play 2.8 兼容

import akka.util.ByteString
import java.io.ByteArrayOutputStream
import org.apache.http.entity.mime.content.StringBody
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.content.FileBody
import org.apache.http.entity.mime.MultipartEntityBuilder
import play.api.http.Writeable
import play.api.libs.Files.TemporaryFile
import play.api.mvc.Codec
import play.api.mvc.MultipartFormData
import play.api.mvc.MultipartFormData.FilePart
import play.api.test.FakeRequest

trait FakeMultipartUpload {

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

    def transform(multipart: MultipartFormData[TemporaryFile]): ByteString = {
      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)
      ByteString(outputStream.toByteArray)
    }

    new Writeable(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: TemporaryFile,
    contentType: String
  ): MultipartFormData[TemporaryFile] = {
    MultipartFormData(
      dataParts = Map(),
      files = Seq(FilePart[TemporaryFile](key, file.file.getName, Some(contentType), file)),
      badParts = Seq()
    )
  }

  /** shortcut for a request body containing a single file attachment */
  case class WrappedFakeRequest[A](fr: FakeRequest[A]) {
    def withFileUpload(key: String, file: TemporaryFile, contentType: String) = {
      fr.withBody(fileUpload(key, file, contentType))
    }
  }
  implicit def toWrappedFakeRequest[A](fr: FakeRequest[A]) = WrappedFakeRequest(fr)
}
于 2021-02-05T19:15:21.513 回答