Skip to content

Instantly share code, notes, and snippets.

@emrecelikten
Last active February 9, 2017 12:47
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save emrecelikten/58ae4a4cb572cfaeb525 to your computer and use it in GitHub Desktop.
Save emrecelikten/58ae4a4cb572cfaeb525 to your computer and use it in GitHub Desktop.
Sample upload testing in Play Framework 2.3.1
package controllers
import play.api.mvc._
object Application extends Controller {
def upload = Action(parse.multipartFormData) {
request =>
if (request.body.files.isEmpty) BadRequest("Invalid file!")
else if (request.body.asFormUrlEncoded.isEmpty) BadRequest("Invalid data!")
else Ok("Everything is okay!")
}
}
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
POST /upload controllers.Application.upload
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
package controllers
import java.io.{ByteArrayOutputStream, File}
import java.nio.charset.Charset
import java.nio.file.{Files, StandardCopyOption, StandardOpenOption}
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.specs2.mutable.Specification
import play.api.http.{ContentTypeOf, Writeable}
import play.api.libs.Files.TemporaryFile
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.{MultipartFormData, Result}
import play.api.test.Helpers._
import play.api.test.{FakeHeaders, FakeRequest, WithApplication}
import scala.concurrent.Future
class UploadTest extends Specification {
// Uses ideas from
// https://stackoverflow.com/questions/13352618/how-to-test-actions-that-expect-an-uploaded-file-in-play-framework-version-2-0
// https://stackoverflow.com/questions/15133794/writing-a-test-case-for-file-uploads-in-play-2-1-and-scala
/**
* An ugly way to create multipartFormData using Apache HTTP MultipartEntityBuilder.
*
* The reason for def instead of a implicit Writeable instance is because we need multipart boundary to pass into
* ContentTypeOf constructor and creating a Writeable requires a ContentTypeOf instance.
*
* @param request fake request on which the Writeable instance will be created
*/
def writeableOf_multipartFormData(request: FakeRequest[MultipartFormData[TemporaryFile]]) = {
val builder = MultipartEntityBuilder.create()
request.body.dataParts.foreach { case (k, vs) => builder.addTextBody(k, vs.mkString)}
// ContentType part is necessary here because it gets parsed as a DataPart otherwise.
request.body.files.foreach { case f => builder.addBinaryBody(f.filename, f.ref.file, ContentType.create(f.contentType.get, null: Charset), f.filename)}
val entity = builder.build()
implicit val contentTypeOf_MultipartFormData: ContentTypeOf[MultipartFormData[TemporaryFile]] = ContentTypeOf[MultipartFormData[TemporaryFile]](Some(entity.getContentType.getValue))
Writeable[MultipartFormData[TemporaryFile]] {
(mfd: MultipartFormData[TemporaryFile]) =>
val outputStream = new ByteArrayOutputStream()
entity.writeTo(outputStream)
outputStream.toByteArray
}
}
/**
* Creates a fake request to given URL and sends it.
*
* @param url url of the controller to send the request
* @param parameters some parameters that you would like to pass as a data part
*/
def sendUploadRequest(url: String, file: File, mimeType: String, parameters: Map[String, String]): Future[Result] = {
// Your original file will be deleted after the controller executes if you don't do the copy part below
val tempFile = TemporaryFile("TEST_REMOVE_")
Files.copy(file.toPath, tempFile.file.toPath, StandardCopyOption.REPLACE_EXISTING)
val part = FilePart[TemporaryFile](key = tempFile.file.getName, filename = tempFile.file.getName, contentType = Some(mimeType), ref = tempFile)
val formData = MultipartFormData(dataParts = parameters.map { case (k, v) => k -> Seq(v)}, files = Seq(part), badParts = Nil, missingFileParts = Nil)
val request = FakeRequest("POST", url, FakeHeaders(), formData)
implicit val writeable = writeableOf_multipartFormData(request)
route(request)(writeable).get
}
"Upload" should {
"pass" in new WithApplication {
val tempFile = TemporaryFile("TEST_")
val fileRef = tempFile.file
Files.write(fileRef.toPath, """{"hello":"world"}""".getBytes, StandardOpenOption.WRITE)
val parameters = Map("foo" -> "bar")
val future = sendUploadRequest(controllers.routes.Application.upload().url, fileRef, "application/json", parameters)
status(future) ==== OK
contentAsString(future) ==== "Everything is okay!"
}
}
}
@Bunyod
Copy link

Bunyod commented Oct 12, 2016

Hi @emrecelikten, I've got a question. Why you should pass writable implicit value as explicitly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment