mercredi 28 novembre 2018

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

I have a simple REST-API. Each api sub-path has its own service implementation.

The questuion is: how to correctly test it?

Example:

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
}

In this case i cannot test my endpoint because of dependency of execution context and service implementation which i should mock. I can create my own implementation of RestApi in test case, but i have to update it each time a change something inside real RestApi

I tried another way:

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
}

In this case, at least, i can test all endpoints but i have to pass execution context and build RestApi object before i can get my routes. Also, this is not the best solution because of now i need to write this new RestApi(dispatcher) extends StubOne with StubTwo with ... and if there is 1 or 2 services - this is ok, but if there is more than 3, than it looks a bit awkward (in my opion).

Than i tried this approach:

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
}

Probably, it is the most common approach, but i still have to pass execution context.

So, the main question is how to test my endpoints which depend on service implementation but without real implementation of those services? I suspect that there is a problem in implementation design but i can still change it. The question it: which approach i should to choose?

Aucun commentaire:

Enregistrer un commentaire