问题 在FakeRequest中使用MultipartFormData进行框架测试


我目前正在为Play Framework 2.2.x应用程序编写一些Specs2测试,该应用程序接受MultipartFormData提交作为其功能的一部分。

我已使用以下格式成功地使用文本和JSON实体编写了其他测试:

"respond to POST JSON with description field present" in {
  running(FakeApplication()) {
    val response = route(FakeRequest(POST, "/submission.json").withJsonBody(toJson(Map("content" -> toJson("test-content"), "description" -> toJson("test-description"))))).get
    status(response) must equalTo(OK)
    contentType(response) must beSome.which(_ == "application/json")
    contentAsString(response) must contain(""""description":"test-description"""")
    contentAsString(response) must contain(""""content":"test-content"""")
  }
}

但是,当我使用.withMultipartFormData方法时,我收到以下错误:

Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
val response = route(FakeRequest(PUT,"/submission.json/1/files").withMultipartFormDataBody(data)).get
                    ^

我试图调试的MultipartFormData测试的形式如下:

"respond to file PUT form with details not specififed" in {
  running(FakeApplication()) {
     val basePath:String = Play.application.path.getCanonicalPath();
     val data:MultipartFormData[TemporaryFile] = MultipartFormData(Map[String,Seq[String]](),
                                    List(
                                        FilePart("file_upload","",Some("Content-Type: multipart/form-data"),TemporaryFile(new java.io.File(basePath + "/test-data/testUpload.jpg")))
                                    ), 
                                    List(), 
                                    List())


     val response = route(FakeRequest(PUT,"/submission.json/1/files").withMultipartFormDataBody(data)).get
     status(response) must equalTo(CREATED)
 }
}

查看相关版本的FakeRequest类的Play Framework文档,我看不到太多帮助我追查问题: play.api.test.FakeRequest 

就其他有关此事的文件而言,Play Framework网站和谷歌似乎相当缺乏。

我尝试了以下尝试测试我的MultipartFormData代码的替代方法:

但是,我也没有任何成功的方法。


6287
2017-10-29 12:52


起源

这个问题就是测试 Helpers 不包括 implicit Writeable[play.api.mvc.AnyContentAsMultipartFormData]。如果你看看Helpers api,你会看到它 route 需要一个 implicit Writeable 并且包装中包含一些预烘焙的。不幸, MultipartFormData不是其中之一。查看 这个相关的SO问题 为可能的解决方案(定义缺失 implicit)。 - eharik


答案:


而不是在一个测试 FakeApplication 这是缓慢的(根据我的经验)当测试并行运行时可能容易出错,我一直都是 单元测试 我的Multipart表单上传处理程序如下:

  1. 拆分Play接线 来自您在控制器中的实际上传处理;例如。:

    def handleUpload = Action(parse.multipartFormData) { implicit request =>
      doUpload(request)
    }
    
    def doUpload(request:Request[MultipartFormData[TemporaryFile]]) = {
      ...
    }
    

    (其中handleUpload是你的方法 routes 处理的文件 POST

  2. 现在你已经有了一个更容易获得的端点,你可以 模拟你的服务层 适当地响应好/坏请求,并将模拟服务注入您正在测试的控制器中(我不会在这里显示,有一百万种不同的方法可以做到这一点)

  3. 现在 模拟出一个多部分请求 那将到达你的 doUpload 方法:

    val request= mock[Request[MultipartFormData[TemporaryFile]]]
    val tempFile = TemporaryFile("do_upload","spec")
    val fileName = "testFile.txt"
    val part = FilePart("key: String", fileName, None, tempFile)
    val files = Seq[FilePart[TemporaryFile]](part)
    val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart](), Seq[MissingFilePart]())
    request.body returns multipartBody 
    
  4. 最后,你可以打电话给你 doUpload 方法和 验证功能

    val result = controller.doUpload(request)
    status(result) must beEqualTo(201)
    

通过这样的测试,您可以快速轻松地测试您的所有错误处理路径 Controller (这可能就是你想要做的事情),而不需要启动整个应用程序的开销。


7
2017-10-29 23:09



更好的设计是创建一个服务来处理上传并将multipart文件发送到该服务。然后,您可以使用所有方案对服务进行单元测试,并且无需在Controller中创建公共的额外方法。除了handleUpload方法没有测试。 - Robert Gabriel
所有好点@RobertGabriel - doUpload 可以宣布 private [controllers] 但你完全正确 - 完全接受单一责任并为此提供服务肯定更好。 - millhouse


(我在另一个帖子中回答: PlayFramework测试:在伪造请求错误中上传文件

简而言之,你需要一个可写的[AnyContentAsMultipartFormData] MultipartFormData[TemporaryFile] 成 Array[Byte],你可以从这里拿走它: http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play


2
2017-07-31 02:47





在Play 2.5.x中,可以轻松测试文件上传

  val file = new java.io.File("the.file")
  val part = FilePart[File](key = "thekey", filename = "the.file", contentType = None, ref = file)
  val request =  FakeRequest().withBody(
    MultipartFormData[File](dataParts = Map.empty, files = Seq(part), badParts = Nil)
  )
  val response = controller.create().apply(request)
  status(response) must beEqualTo(201)

1
2017-10-17 05:19



这解决了我没有Materializer的问题。谢谢。我曾有一个 could not find implicit value for parameter mat: akka.stream.Materializer 导致我使用的错误 GuiceOneAppPerSuite。 GuiceOneAppPerSuite 有一个 Materializer 但没有执行上下文。所以我的下一个错误是 NoMaterializer does not provide an ExecutionContext。在我遇到这个建议之前,这似乎是一个死胡同。所以我有一个问题为什么 val request = FakeRequest().withMultipartFormDataBody(multipartData) 不起作用但是 val request = FakeRequest().withBody(multipartData) 呢? - gcaliari