如何测试 akka-http 路由来处理异常?

问题描述

我有一个简单的 akka-http 应用程序,可以将文件分块上传到服务器。它有两个路由,第一个 / 打开一个 HTML 表单来搜索文件,第二个路由将上传按钮链接到将文件分成块并上传的逻辑。然后我构建了一个单元测试(a.k.a. Spec)来测试这两条路线。第一个很简单,它只是识别我可以打开网页。但我想测试的第二个:

  1. 如果表单上没有文件,我应该在规范中识别异常
  2. 如果表单上有文件,我会检查逻辑并测试它是否真的上传文件

所以,这是我的 akka-http 应用程序:

import akka.Done
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes,httpentity,Multipart}
import akka.http.scaladsl.server.Directives._
import akka.stream.ThrottleMode
import akka.stream.scaladsl.{FileIO,Sink,Source}
import akka.util.ByteString

import java.io.File
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.{Failure,Success}

object UploadingFiles {
  implicit val system = ActorSystem("UploadingFiles")
  val NO_OF_MESSAGES = 1
  val filesRoutes = {
    (pathEndOrSingleSlash & get) {
      complete(
        httpentity(
          ContentTypes.`text/html(UTF-8)`,"""
            |<html>
            |  <body>
            |    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
            |      <input type="file" name="myFile" multiple>
            |      <button type="submit">Upload</button>
            |    </form>
            |  </body>
            |</html>
          """.stripMargin
        )
      )
    } ~ (path("upload") & post & extractLog) { log =>
      // handling uploading files using multipart/form-data
      entity(as[Multipart.FormData]) { formData =>
        // handle file payload
        val partsSource: Source[Multipart.FormData.BodyPart,Any] = formData.parts
        val filePartsSink: Sink[Multipart.FormData.BodyPart,Future[Done]] =
          Sink.foreach[Multipart.FormData.BodyPart] { bodyPart =>
            if (bodyPart.name == "myFile") {
              // create a file
              val filename = "download/" + bodyPart.filename.getorElse("tempFile_" + System.currentTimeMillis())
              val file = new File(filename)

              log.info(s"writing to file: $filename")
              val fileContentsSource: Source[ByteString,_] = bodyPart.entity.dataBytes
              val fileContentsSink: Sink[ByteString,_] = FileIO.toPath(file.toPath)

              val publishRate = NO_OF_MESSAGES / 1
              // writing the data to the file using akka-stream graph
              fileContentsSource
                .throttle(publishRate,2 seconds,publishRate,ThrottleMode.shaping)
                .runWith(fileContentsSink)
            }
          }
        val writeOperationFuture = partsSource.runWith(filePartsSink)
        onComplete(writeOperationFuture) {
          case Success(value) => complete("file uploaded =)")
          case Failure(exception) => complete(s"file Failed to upload: $exception")
        }
      }
    }
  }

  def main(args: Array[String]): Unit = {
    println("access the browser at: localhost:8080")
    Http().newServerAt("localhost",8080).bindFlow(filesRoutes)
  }
}

我想在我的规范中的 akka-http 应用程序上捕获带有异常 s"file Failed to upload: $exception" 的字符串:

import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.{RouteTestTimeout,ScalatestRouteTest}
import org.github.felipegutierrez.explore.akka.classic.http.server.highlevel.UploadingFiles.filesRoutes
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.concurrent.duration._

class UploadingFilesspec extends AnyWordSpec
  with Matchers
  with ScalatestRouteTest {

  implicit val timeout: RouteTestTimeout = RouteTestTimeout(2 seconds)

  "A basic GET request to open the html form" should {
    "return OK [200]" in {
      Get("/") ~> filesRoutes ~> check {
        status shouldBe StatusCodes.OK
      }
    }
  }
  "A POST request upload without a file" should {
    "return NOT OK" in {
      Post("/upload") ~> filesRoutes ~> check {
        handled should ===(false) // THIS WORKS

        // THIS DOES NOT WORK
        rejection should ===(UnsupportedRequestContentTypeRejection(Set(ContentTypes.`text/plain(UTF-8)`)))
        println(rejections)
        rejections.foreach { e: Rejection =>
          println(e.toString)
          println(e.asInstanceOf[UnsupportedRequestContentTypeRejection].contentType)
        }
        // THIS DOES NOT WORK
        rejection should ===(
          UnsupportedRequestContentTypeRejection(
            Set(ContentTypes.`text/plain(UTF-8)`),Some(ContentTypes.`text/plain(UTF-8)`)
          )
        )
      }
    }
  }
}

但似乎测试套件正在测试异常的全部内容,并且我想使其成为任何类型异常的通用。这是错误

akka.http.scaladsl.server.UnsupportedRequestContentTypeRejection@bdc8014 did not equal akka.http.scaladsl.server.UnsupportedRequestContentTypeRejection@1f443fae
ScalaTestFailureLocation: org.github.felipegutierrez.explore.akka.classic.http.server.highlevel.UploadingFilesspec at (UploadingFilesspec.scala:30)
Expected :akka.http.scaladsl.server.UnsupportedRequestContentTypeRejection@1f443fae
Actual   :akka.http.scaladsl.server.UnsupportedRequestContentTypeRejection@bdc8014

解决方法

请注意,Multipart.FormData,正如您在路由中所期望的那样,具有以下媒体类型:

def mediaType = MediaTypes.`multipart/form-data`

从它创建 ContentTypeRange 的方法是:

ContentTypeRange(MediaRange.apply(MediaTypes.`multipart/form-data`))

并且它没有内容类型。

因此,以下应该有效:

"A POST request upload without a file" should {
  "return NOT OK" in {
    Post("/upload") ~> filesRoutes ~> check {
      handled should ===(false) // THIS WORKS

      rejection should ===(UnsupportedRequestContentTypeRejection(
        Set(ContentTypeRange(MediaRange.apply(MediaTypes.`multipart/form-data`))),Some(ContentTypes.NoContentType))
      )
    }
  }
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...