如何使用akka-http设计一个restapi方便测试?

How to design a rest api using akka-http so that it is easy to test?

我有一个简单的 REST-API。每个 api 子路径都有自己的服务实现。

问题是:如何正确测试呢?

示例:

class RestAPI(implicit dispatcher: ExecutionContext)  // some services need its own dispatcher
extends FirstService with SecondService with etc... { 
  val api: Route = pathPrefix("api") {
    get {
      firstService()
    } ~ post {
      secondService()
    } ~ ...
  }

  def start(implicit system: ActorSystem, materializer: ActorMaterializer): Unit = {
    Http().bindAndHandle(api, "host", 8080)
  }
}

object RestAPI {
  def apply(implicit dispatcher: ExecutionContext): RestAPI = new RestAPI
}

在这种情况下,由于我应该模拟的执行上下文和服务实现的依赖性,我无法测试我的端点。我可以在测试用例中创建自己的 RestApi 实现,但每次在真实 RestApi 中进行更改时我都必须更新它

我尝试了另一种方式:

class RestAPI(implicit dispatcher: ExecutionContext)  { // some services need its own dispatcher
  this: FirstService with SecondService with etc... =>
  val api: Route = pathPrefix("api") {
    get {
      firstService()
    } ~ post {
      secondService()
    } ~ ...
  }

  def start(implicit system: ActorSystem, materializer: ActorMaterializer): Unit = {
    Http().bindAndHandle(api, "host", 8080)
  }
}

object RestAPI {
  def apply(implicit dispatcher: ExecutionContext): RestAPI = new RestAPI extends DefaultFirstService with DefaultSecondService with etc...
}


Test {
  val api = (new RestApi(dispatcher) extends StubOne with StubTwo with ...).api
}

在这种情况下,至少,我可以测试所有端点,但我必须先传递执行上下文并构建 RestApi 对象,然后才能获取我的路由。此外,这不是最好的解决方案,因为现在我需要写这个 new RestApi(dispatcher) extends StubOne with StubTwo with ... 如果有 1 或 2 个服务 - 这没问题,但如果超过 3 个,它看起来有点尴尬(在我的意见)。

比起我试过这​​个方法:

class RestAPI(serviceOne: FirstService, serviceTwo: SecondService, ...)(implicit dispatcher: ExecutionContext)  { // some services need its own dispatcher
  val api: Route = pathPrefix("api") {
    get {
      serviceOne.firstService()
    } ~ post {
      serviceTwo.secondService()
    } ~ ...
  }

  def start(implicit system: ActorSystem, materializer: ActorMaterializer): Unit = {
    Http().bindAndHandle(api, "host", 8080)
  }
}

object RestAPI {
  def apply(serviceOne: FirstService, serviceTwo: SecondService, ...)(implicit dispatcher: ExecutionContext): RestAPI = new RestAPI(serviceOne, serviceTwo, ...)
}


Test {
  val api = (new RestApi(...)(dispatcher)).api
}

可能这是最常见的方法,但我仍然必须传递执行上下文。

所以,主要问题是如何测试依赖于服务实现但没有真正实现这些服务的端点?我怀疑实现设计有问题,但我仍然可以更改它。问题是:我应该选择哪种方法?

我猜你需要 运行 Http().bindAndHandle(api, "host", 8080) 的执行上下文,所以我会在不同的 class.

中执行此操作

如果你这样做,你可以使用你的方法 #3,使用 mockito-scala 来提供你的依赖的模拟,一旦你有一个完全构造的 RestAPI 实例,就把它传递到任何地方您已将 http 绑定代码移至 仅此而已。

总而言之,将 API 定义与 Http 服务器初始化分开,模拟您的依赖项并开心!