【问题标题】:Akka Http: how to test a route with a flow to 3rd party service?Akka Http:如何测试带有流向 3rd 方服务的路由?
【发布时间】:2017-06-20 13:10:12
【问题描述】:

我在 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")
  }

  }

所以问题是如何更好地测试这条路线,记住带有流的演员也存在于它的底层。

【问题讨论】:

  • 在您的测试中实例化一个模拟 Web http 服务器并接听电话?
  • @DiegoMartinoia 是的,这是最明显的情况。如果在这种情况下没有其他方法可行,我会这样做。实际上,我正在为 akka 流寻找一些测试技术。类似 Flow 用假的替换......你怎么看?
  • 依赖注入和模拟的问题是,如果“真正的”依赖被破坏,您的测试不会失败。我在这方面是少数,但特别是当涉及到远程 http 调用等复杂的事情时,我喜欢把事情黑箱化。尤其是 Akka 应用程序通常会包含许多您想要测试的“奇怪”的东西(持久性、分片、集群),我发现实际旋转应用程序并在末端探测它更容易。但那是我

标签: akka akka-http akka-stream akka-testkit


【解决方案1】:

作曲

按照目前的组织方式,测试路由逻辑的一个困难是很难隔离功能。没有Actor 就无法测试您的Route 逻辑,并且很难在没有路由的情况下测试您的Actor 查询。

我认为函数组合会更好地为您服务,这样您就可以隔离您正在尝试测试的内容。

先抽象掉Actor查询(询问):

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,并且您可以在不需要演员的情况下测试createRoute 方法!

您可以使用始终返回预定义令牌的注入函数来测试 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
}

或者,您可以使用从不返回令牌的注入函数来测试 createRoute:

val alwaysFailsRoute = createRoute(_ => Success(AccessTokenError))

Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysFailsRoute ~> check {
  status shouldEqual StatusCodes.InternalServerError
  responseAs[String] shouldEqual errorMsg
}

【讨论】:

  • 很棒的方法 :) 到最后,我明白我的错误是将所有东西放在一起。我需要更多独立的组件!
猜你喜欢
  • 2017-10-23
  • 1970-01-01
  • 2021-05-07
  • 2017-10-24
  • 1970-01-01
  • 2022-01-22
  • 2020-04-17
  • 2018-08-15
  • 1970-01-01
相关资源
最近更新 更多