2013-02-28 1 views
10

Schreiben fand ich die folgende Frage/Antwort:einen Testfall für das Hochladen von Dateien in Play 2.1 und Scala

Test MultipartFormData in Play 2.0 FakeRequest

Aber es scheint, die Dinge in Play 2.1 geändert haben. Ich habe versucht, wie das Beispiel der Anpassung 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") 

Allerdings, wenn ich versuche, um die Anforderung zu laufen, ich habe eine Null-Zeiger-Ausnahme erhalten:

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

Wenn ich versuche, den veralteten routeAndCall zu ersetzen einfach weiterleiten (und das Option around-Ergebnis entfernen), bekomme ich einen Kompilierfehler, der besagt, dass es keine Instanz von MultipartFormData [TemporaryFile] in die HTTP-Antwort schreiben kann.

Was ist der richtige Weg, um diesen Test in Play 2.1 mit Scala zu entwickeln?


bearbeiten: Versucht, den Code zu ändern, nur die Steuerung zu testen:

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

Aber ich nun einen Typfehler auf allen Testbedingungen um die Ergebnisse wie so erhalten:

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

Ich verstehe nicht, warum ich einen Interoperator für Byte-Arrays auf Ergebnisse zugeordnet bin. Könnte das etwas damit zu tun haben, wie ich einen benutzerdefinierten Body Parser verwende? Mein Controller-Definition sieht wie folgt aus:

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

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

Mit dem Formular-Parser aus diesem Beitrag: Pulling files from MultipartFormData in memory in Play2/Scala

+0

Eine ähnliche Frage gestellt und beantwortet wurde: http://stackoverflow.com/questions/15013177/serializing-multipart-form-requests-for-testing -on-play-2-1/15013786 # 15013786 – EECOLOR

+0

Ich sehe die Frage und Antwort, aber es ist immer noch wirklich verwirrend und nicht gut beantwortet. Sie verweisen auf die offizielle Dokumentation, die ich gelesen habe und die keine mehrteiligen Formulardaten abdeckt. Ich würde auch gerne die Route testen, aber ich denke, das Testen des Controllers wird es tun. Ich verstehe immer noch nicht, wie man die Datei mit dem Namen "qqfile" an den Körper weitergibt. Könnten Sie Ihre Frage mit einer vollständigen Antwort bearbeiten? – djsumdog

+0

Es wurde versucht, nur den Controller zu testen, aber es traten dennoch einige Probleme auf. Bearbeiten ist oben aufgeführt. – djsumdog

Antwort

14

Play 2.3 enthält eine neuere Version von httpmime.jar, die einige kleinere Korrekturen erfordert. Aufbauend auf der Lösung von Marcus mit dem schreibbaren Mechanismus von Play, während ein Teil des syntaktischen Zuckers aus meinem Play 2 erhalten bleibt.1-Lösung, ist das, was ich kommen mit habe:

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

Danke! Das hat mir viele Stunden erspart ... –

+0

Danke, funktioniert ein Leckerbissen auf 2.3 und ersparte mir viel Mühe. – Mikesname

+0

Kann bestätigen, dass dies auch auf 2.4 funktioniert. Ich frage mich, warum gibt es keine Schreibbar für MultipartFormData in Helpers ... –

13

ich mit Play 2.1 diese Funktion erhalten verwaltet, basierend auf verschiedenen Mailingliste Vorschläge. Hier ist, wie ich es tun:

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

Ich werde es versuchen. Ich habe eine Lösung gepostet, mit der ich auch arbeiten kann. – djsumdog

+0

Kann es nicht funktionieren lassen. Ausnahmebedingung 'IllegalArgumentException: Protokoll = http Host = null'. Zurzeit wird 'commons-httpclient"% "commons-httpclient"% "3.1" 'verwendet. Ist das der Schuldige oder ist es etwas anderes? –

+0

Das hat für mich geklappt. –

2

EEColor Vorschlag folgend, habe ich folgendes zu arbeiten:

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


    } 
+1

Es ist erwähnenswert, dass diese Lösung den Router und die Serialisierung von Play umgeht, während die von Alex dies nicht tut. –

+0

Für mich funktioniert dieser hier immer noch nicht, während Alex 'Version funktioniert und sauberer aussieht. Sie können es wiederverwenden. Deine Version beschwert sich darüber, dass du keinen Array [Byte] Writer hast ... – Herman

5

Ich habe Alex Code geändert als beschreibbar zu wirken, die besser integriert ins Spiel 2.2.2

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

auf diese Weise ist es möglich, so etwas zu schreiben:

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 
+0

Wenn du etwas wie [this] (https://gist.github.com/mkantor/fea79a7152050d9bb03f) zu dem oben genannten hinzufügst, kannst du Play '' FakeRequest verwenden() .withMultipartFormDataBody() 'Methode. Es ist seltsam, dass es aus der Box gebrochen ist. –

1

Hier ist meine Version von Beschreibbare [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) 
} 

Es ist angepasst aus (und einige Fehler behoben): https://github.com/jroper/playframework/blob/multpart-form-data-writeable/framework/src/play/src/main/scala/play/api/http/Writeable.scala#L108

Sehen Sie die ganze Post hier, wenn Sie interessiert sind: http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play

1

Für mich ist die beste Lösung für dieses Problem die Alex Varju one

Hier ist eine Version für Play aktualisiert 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)) 
    } 
} 
Verwandte Themen