如何测试 akka-http 路由来处理异常?
How to test akka-http route to handle exception?
我有一个简单的 akka-http 应用程序可以将文件分块上传到服务器。它有两个路由,第一个 /
打开一个 HTML 表单来搜索文件,第二个路由将上传按钮链接到将文件分成块并上传的逻辑。然后我构建了一个单元测试(a.k.a.Spec)来测试这两条路由。第一个很简单,就是识别我可以打开网页。但第二个我想测试:
- 如果表格上没有文件,我应该在规范中确定例外情况
- 如果表单上有文件,我会检查逻辑并测试它是否真的上传了文件。
所以,这是我的 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)
}
}
我想在我的 Spec:
中的 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))
)
}
}
}
我有一个简单的 akka-http 应用程序可以将文件分块上传到服务器。它有两个路由,第一个 /
打开一个 HTML 表单来搜索文件,第二个路由将上传按钮链接到将文件分成块并上传的逻辑。然后我构建了一个单元测试(a.k.a.Spec)来测试这两条路由。第一个很简单,就是识别我可以打开网页。但第二个我想测试:
- 如果表格上没有文件,我应该在规范中确定例外情况
- 如果表单上有文件,我会检查逻辑并测试它是否真的上传了文件。
所以,这是我的 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)
}
}
我想在我的 Spec:
中的 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))
)
}
}
}