scala 如何测试客户端 Akka HTTP
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34714931/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to test client-side Akka HTTP
提问by steinybot
I've just started testing out the Akka HTTP Request-Level Client-Side API (Future-Based). One thing I've been struggling to figure out is how to write a unit test for this. Is there a way to mock the response and have the future completed without having to actually do an HTTP request?
我刚刚开始测试Akka HTTP Request-Level Client-Side API (Future-Based)。我一直在努力弄清楚的一件事是如何为此编写单元测试。有没有办法模拟响应并完成未来而无需实际执行 HTTP 请求?
I was looking at the API and the testkit package, trying to see how I could use that, only to find in the docs that it actually says:
我正在查看 API 和 testkit 包,试图了解如何使用它,却在文档中发现它实际上是这样说的:
akka-http-testkitA test harness and set of utilities for verifying server-side service implementations
akka-http-testkit用于验证服务器端服务实现的测试工具和实用程序集
I was thinking something TestServer(kinda like the TestSourcefor Akka Streams) and use the server side routing DSL to create the expected response and somehow hook this up the the Httpobject.
我在想一些事情TestServer(有点像TestSourceAkka Streams)并使用服务器端路由 DSL 来创建预期的响应并以某种方式将它连接到Http对象。
Here is a simplified example of what the function does that I want to test:
这是我要测试的函数功能的简化示例:
object S3Bucket {
def sampleTextFile(uri: Uri)(
implicit akkaSystem: ActorSystem,
akkaMaterializer: ActorMaterializer
): Future[String] = {
val request = Http().singleRequest(HttpRequest(uri = uri))
request.map { response => Unmarshal(response.entity).to[String] }
}
}
回答by mattinbits
I think in general terms you've already hit on the fact that the best approach is to mock the response. In Scala, this can be done using Scala Mock http://scalamock.org/
我认为一般来说,您已经意识到最好的方法是模拟响应。在 Scala 中,这可以使用 Scala Mock http://scalamock.org/来完成
If you arrange your code so that your instance of akka.http.scaladsl.HttpExtis dependency injected into the code which uses it (e.g. as a constructor parameter), then during testing you can inject an instance of mock[HttpExt]rather than one built using the Httpapply method.
如果你安排你的代码,你的实例akka.http.scaladsl.HttpExt被依赖注入到使用它的代码中(例如作为构造函数参数),那么在测试期间你可以注入一个实例mock[HttpExt]而不是一个使用Httpapply 方法构建的实例。
EDIT: I guess this was voted down for not being specific enough. Here is how I would structure the mocking of your scenario. It is made a little more complicated by all the implicitis.
编辑:我想这是因为不够具体而被否决。这是我将如何构建您的场景的模拟。所有的隐式都使它变得更加复杂。
Code in main:
代码main:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{Uri, HttpResponse, HttpRequest}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import scala.concurrent.{ExecutionContext, Future}
trait S3BucketTrait {
type HttpResponder = HttpRequest => Future[HttpResponse]
def responder: HttpResponder
implicit def actorSystem: ActorSystem
implicit def actorMaterializer: ActorMaterializer
implicit def ec: ExecutionContext
def sampleTextFile(uri: Uri): Future[String] = {
val responseF = responder(HttpRequest(uri = uri))
responseF.flatMap { response => Unmarshal(response.entity).to[String] }
}
}
class S3Bucket(implicit val actorSystem: ActorSystem, val actorMaterializer: ActorMaterializer) extends S3BucketTrait {
override val ec: ExecutionContext = actorSystem.dispatcher
override def responder = Http().singleRequest(_)
}
Code in test:
代码test:
import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.testkit.TestKit
import org.scalatest.{BeforeAndAfterAll, WordSpecLike, Matchers}
import org.scalamock.scalatest.MockFactory
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.Future
class S3BucketSpec extends TestKit(ActorSystem("S3BucketSpec"))
with WordSpecLike with Matchers with MockFactory with BeforeAndAfterAll {
class MockS3Bucket(reqRespPairs: Seq[(Uri, String)]) extends S3BucketTrait{
override implicit val actorSystem = system
override implicit val ec = actorSystem.dispatcher
override implicit val actorMaterializer = ActorMaterializer()(system)
val mock = mockFunction[HttpRequest, Future[HttpResponse]]
override val responder: HttpResponder = mock
reqRespPairs.foreach{
case (uri, respString) =>
val req = HttpRequest(HttpMethods.GET, uri)
val resp = HttpResponse(status = StatusCodes.OK, entity = respString)
mock.expects(req).returning(Future.successful(resp))
}
}
"S3Bucket" should {
"Marshall responses to Strings" in {
val mock = new MockS3Bucket(Seq((Uri("http://example.com/1"), "Response 1"), (Uri("http://example.com/2"), "Response 2")))
Await.result(mock.sampleTextFile("http://example.com/1"), 1 second) should be ("Response 1")
Await.result(mock.sampleTextFile("http://example.com/2"), 1 second) should be ("Response 2")
}
}
override def afterAll(): Unit = {
val termination = system.terminate()
Await.ready(termination, Duration.Inf)
}
}
build.sbtdependencies:
build.sbt依赖项:
libraryDependencies += "com.typesafe.akka" % "akka-http-experimental_2.11" % "2.0.1"
libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.2" % "test"
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.6"
libraryDependencies += "com.typesafe.akka" % "akka-testkit_2.11" % "2.4.1"
回答by Maxim Plevako
Considering that you indeed want to write a unit test for your HTTP client you should pretend there is no real server and not cross the network boundary, otherwise you will obviously do integration tests. A long known recipe of enforcing a unit-testable separation in such cases as yours is to split interface and implementation. Just define an interface abstracting access to an external HTTP server and its real and fake implementations as in the following sketch
考虑到您确实想为您的 HTTP 客户端编写单元测试,您应该假装没有真正的服务器并且不跨越网络边界,否则您显然会进行集成测试。在您的这种情况下强制执行单元可测试分离的一个长期已知的方法是拆分接口和实现。只需定义一个抽象访问外部 HTTP 服务器及其真实和虚假实现的接口,如下面的草图所示
import akka.actor.Actor
import akka.pattern.pipe
import akka.http.scaladsl.HttpExt
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
import scala.concurrent.Future
trait HTTPServer {
def sendRequest: Future[HttpResponse]
}
class FakeServer extends HTTPServer {
override def sendRequest: Future[HttpResponse] =
Future.successful(HttpResponse(StatusCodes.OK))
}
class RealServer extends HTTPServer {
def http: HttpExt = ??? //can be passed as a constructor parameter for example
override def sendRequest: Future[HttpResponse] =
http.singleRequest(HttpRequest(???))
}
class HTTPClientActor(httpServer: HTTPServer) extends Actor {
override def preStart(): Unit = {
import context.dispatcher
httpServer.sendRequest pipeTo self
}
override def receive: Receive = ???
}
and test your HTTPClientActorin conjunction with FakeServer.
并HTTPClientActor结合FakeServer.
回答by steinybot
I was hoping there might be a way to leverage some sort of test actor system but in the absence of that (or some other idiomatic way) I am probably going to do something like this:
我希望可能有一种方法来利用某种测试演员系统,但在没有这种情况下(或其他一些惯用的方式),我可能会做这样的事情:
object S3Bucket {
type HttpResponder = HttpRequest => Future[HttpResponse]
def defaultResponder = Http().singleRequest(_)
def sampleTextFile(uri: Uri)(
implicit akkaSystem: ActorSystem,
akkaMaterializer: ActorMaterializer,
responder: HttpResponder = defaultResponder
): Future[String] = {
val request = responder(HttpRequest(uri = uri))
request.map { response => Unmarshal(response.entity).to[String] }
}
}
Then in my test I can just provide a mock HttpResponder.
然后在我的测试中,我可以提供一个模拟HttpResponder.

