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

I managed to get this working with Play 2.1 based on various mailing list suggestions. Here's how I do it:

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

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

I've modified Alex's code to act as a Writable which better integrates into Play 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))
  }
}

This way it is possible to write something like this:

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