Akka Http:如何测试流向 3rd 方服务的路由?
Akka Http: how to test a route with a flow to 3rd party service?
我在 akka-http 应用程序中有一个路由,它通过 Http().cachedHostConnectionPoolHttps
与第三方服务集成。我想以正确的方式测试它。但不确定应该如何:(
这是这条路线的样子:
val routes: Route = pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(accessTokenActor ? GetAccessToken(userId, refreshToken)) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError("There was problems while retriving the access token"))
}
}
}
}
这条路线的背后隐藏着 accessTokenActor
所有逻辑发生的地方,这里是:
class AccessTokenActor extends Actor with ActorLogging with APIConfig {
implicit val actorSystem = context.system
import context.dispatcher
implicit val materializer = ActorMaterializer()
import AccessTokenActor._
val connectionFlow = Http().cachedHostConnectionPoolHttps[String]("www.service.token.provider.com")
override def receive: Receive = {
case get: GetAccessToken => {
val senderActor = sender()
Source.fromFuture(Future.successful(
HttpRequest(
HttpMethods.GET,
"/oauth2/token",
Nil,
FormData(Map(
"clientId" -> youtubeClientId,"clientSecret" -> youtubeSecret,"refreshToken" -> get.refreshToken))
.toEntity(HttpCharsets.`UTF-8`)) -> get.channelId
)
)
.via(connectionFlow)
.map {
case (Success(resp), id) => resp.status match {
case StatusCodes.OK => Unmarshal(resp.entity).to[AccessTokenModel]
.map(senderActor ! AccessToken(_.access_token))
case _ => senderActor ! AccessTokenError
}
case _ => senderActor ! AccessTokenError
}
}.runWith(Sink.head)
case _ => log.info("Unknown message")
}
}
所以问题是如何更好地测试这条路线,请记住,具有流的演员也存在于它的幕后。
作文
按照目前的组织方式,测试路由逻辑的一个困难是很难隔离功能。没有 Actor
就不可能测试你的 Route
逻辑,没有路由就很难测试你的 Actor 查询。
我认为您最好使用函数组合,这样您就可以隔离您要测试的内容。
先抽象掉Actor
查询(ask):
sealed trait TokenResponse
case class AccessToken() extends TokenResponse {...}
case object AccessTokenError extends TokenResponse
val queryActorForToken : (ActorRef) => (GetAccessToken) => Future[TokenResponse] =
(ref) => (getAccessToken) => (ref ? getAccessToken).mapTo[TokenResponse]
现在将您的 routes
值转换为将查询函数作为参数的高阶方法:
val actorRef : ActorRef = ??? //not shown in question
type TokenQuery = GetAccessToken => Future[TokenResponse]
val actorTokenQuery : TokenQuery = queryActorForToken(actorRef)
val errorMsg = "There was problems while retriving the access token"
def createRoute(getToken : TokenQuery = actorTokenQuery) : Route =
pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(getToken(GetAccessToken(userId, refreshToken))) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError(errorMsg))
}
}
}
}
//original routes
val routes = createRoute()
测试
现在您可以在不需要 Route
的情况下测试 queryActorForToken
并且您可以在不需要 actor 的情况下测试 createRoute
方法!
您可以使用总是 returns 预定义标记的注入函数测试 createRoute:
val testToken : AccessToken = ???
val alwaysSuccceedsRoute = createRoute(_ => Success(testToken))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysSucceedsRoute ~> check {
status shouldEqual StatusCodes.Ok
responseAs[String] shouldEqual testToken.toJson
}
或者,您可以使用从未 returns 标记的注入函数测试 createRoute:
val alwaysFailsRoute = createRoute(_ => Success(AccessTokenError))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysFailsRoute ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual errorMsg
}
我在 akka-http 应用程序中有一个路由,它通过 Http().cachedHostConnectionPoolHttps
与第三方服务集成。我想以正确的方式测试它。但不确定应该如何:(
这是这条路线的样子:
val routes: Route = pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(accessTokenActor ? GetAccessToken(userId, refreshToken)) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError("There was problems while retriving the access token"))
}
}
}
}
这条路线的背后隐藏着 accessTokenActor
所有逻辑发生的地方,这里是:
class AccessTokenActor extends Actor with ActorLogging with APIConfig {
implicit val actorSystem = context.system
import context.dispatcher
implicit val materializer = ActorMaterializer()
import AccessTokenActor._
val connectionFlow = Http().cachedHostConnectionPoolHttps[String]("www.service.token.provider.com")
override def receive: Receive = {
case get: GetAccessToken => {
val senderActor = sender()
Source.fromFuture(Future.successful(
HttpRequest(
HttpMethods.GET,
"/oauth2/token",
Nil,
FormData(Map(
"clientId" -> youtubeClientId,"clientSecret" -> youtubeSecret,"refreshToken" -> get.refreshToken))
.toEntity(HttpCharsets.`UTF-8`)) -> get.channelId
)
)
.via(connectionFlow)
.map {
case (Success(resp), id) => resp.status match {
case StatusCodes.OK => Unmarshal(resp.entity).to[AccessTokenModel]
.map(senderActor ! AccessToken(_.access_token))
case _ => senderActor ! AccessTokenError
}
case _ => senderActor ! AccessTokenError
}
}.runWith(Sink.head)
case _ => log.info("Unknown message")
}
}
所以问题是如何更好地测试这条路线,请记住,具有流的演员也存在于它的幕后。
作文
按照目前的组织方式,测试路由逻辑的一个困难是很难隔离功能。没有 Actor
就不可能测试你的 Route
逻辑,没有路由就很难测试你的 Actor 查询。
我认为您最好使用函数组合,这样您就可以隔离您要测试的内容。
先抽象掉Actor
查询(ask):
sealed trait TokenResponse
case class AccessToken() extends TokenResponse {...}
case object AccessTokenError extends TokenResponse
val queryActorForToken : (ActorRef) => (GetAccessToken) => Future[TokenResponse] =
(ref) => (getAccessToken) => (ref ? getAccessToken).mapTo[TokenResponse]
现在将您的 routes
值转换为将查询函数作为参数的高阶方法:
val actorRef : ActorRef = ??? //not shown in question
type TokenQuery = GetAccessToken => Future[TokenResponse]
val actorTokenQuery : TokenQuery = queryActorForToken(actorRef)
val errorMsg = "There was problems while retriving the access token"
def createRoute(getToken : TokenQuery = actorTokenQuery) : Route =
pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(getToken(GetAccessToken(userId, refreshToken))) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError(errorMsg))
}
}
}
}
//original routes
val routes = createRoute()
测试
现在您可以在不需要 Route
的情况下测试 queryActorForToken
并且您可以在不需要 actor 的情况下测试 createRoute
方法!
您可以使用总是 returns 预定义标记的注入函数测试 createRoute:
val testToken : AccessToken = ???
val alwaysSuccceedsRoute = createRoute(_ => Success(testToken))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysSucceedsRoute ~> check {
status shouldEqual StatusCodes.Ok
responseAs[String] shouldEqual testToken.toJson
}
或者,您可以使用从未 returns 标记的注入函数测试 createRoute:
val alwaysFailsRoute = createRoute(_ => Success(AccessTokenError))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysFailsRoute ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual errorMsg
}