Каковы способы модульного тестирования клиентов Rest API - то есть проверки того, что правильные запросы Rest отправлены - PullRequest
0 голосов
/ 04 июля 2018

На работе мы разрабатываем сервис, который находится между двумя другими сервисами. Мы раскрываем Rest API, который вызывается какой-то службой - запрос обрабатывается некоторой логикой, а затем, согласно логике, HTTP-запросы будут отправляться другой службой.

В Интернете множество ресурсов о том, как наилучшим образом тестировать конечные точки API, которые вы предоставляете. С другой стороны, я хочу проверить, какие запросы API отправляются, не отправляя их фактической службе.

Полагаю, я всегда мог настроить весь серверный скелет на localhost: 8080, который просто записывает то, что он получает, но это выглядит немного грязно. Тестирование Rest API, которое мы предоставляем для внешних сервисов (для этого мы используем akka-http), довольно удобно тестировать с помощью akka-http-testkit, что отлично. Мне было просто интересно, есть ли какой-нибудь сравнительно удобный инструмент для проверки того, что Http требует.

Ответы [ 2 ]

0 голосов
/ 09 июля 2018

Функциональное программирование "Инструмент"

Самый простой способ, который я нашел для тестирования этих сценариев, - это использовать в своем дизайне простые старые принципы функционального программирования. Вы можете встроить ваше Route создание в функцию более высокого порядка. Эта функция более высокого порядка будет принимать функцию, которая запрашивает нисходящий сервис:

type ComputedData = ???

val computeData : HttpRequest => ComputedData = ???

def intermediateRoute(downstreamService : ComputedData => Future[HttpResponse]) : Route = 
  extractRequest { request =>
    val computedData : ComputedData = computeData(request)

    complete(downstreamService(computedData))
  }

Эта функция более высокого порядка теперь может использоваться в производстве:

val queryDownStreamService : ComputedData => Future[HttpResponse] = ???

val productionRoute : Route = intermediateRoute(queryDownStreamService)

Или его можно использовать в модульном тестировании для проверки правильности логики:

val testComputedData : ComputedData => Boolean = ???

val testResponse : HttpResponse = ???

val testService : ComputedData => Future[HttpResponse] = (computedData) => {
  assert(testComputedData(computedData))

  Success(testResponse)
}

val testRoute = intermediateRoute(testService)

Get("/test") ~> testRoute ~> check {
  response should be testResponse
}
0 голосов
/ 04 июля 2018

Мы делаем это так, как вы называете dirty, хотя я не думаю, что это dirty.

у нас есть базовая черта, которая запускает / выключает сервер (мы используем http4s и scalatest)

trait EmbeddedServer extends BeforeAndAfterAll with Http4sDsl[IO] {
  self: Suite =>

  private var server: Server[IO] = _

  protected var lastRequest: Request[IO] = _

  private def captureRequest: HttpService[IO] = Kleisli { req: Request[IO] =>
    lastRequest = req
    service(req)
  }

  override protected def beforeAll(): Unit = {
    server = BlazeBuilder[IO]
      .bindAny()
      .mountService(captureRequest, "/")
      .start
      .unsafeRunSync()
    super.beforeAll()
  }

  override protected def afterAll(): Unit = {
    super.afterAll()
    server.shutdownNow()
  }

  def address: InetSocketAddress = server.address

  def rootURI: String = s"http:/$address"

  def service: HttpService[IO]

}

тогда мы смешиваем это в нашей client спецификации

что-то в этом роде

class SomeRequesterSpec extends WordSpec with EmbeddedServer {

  override def service: HttpService[IO] = HttpService[IO] {
    case GET -> Root / "failure" => ServiceUnavailable()
    case GET -> Root / "success" => Ok(SuccessBody)
    case GET -> Root / "partial-success" => Ok(PartialSuccessBody)
    case GET -> Root / "malformed" => Ok(MalformedBody)
    case GET -> Root / "empty" => Ok(EmptyResponse)
  }

  //... you specs go here

}

и в своих спецификациях вы звоните на сервер mocked со своим клиентом используя s"$rootURI/success" или s"$rootURI/failure" конечные точки и проверьте, правильно ли он обрабатывает ответы. Также lastRequest var всегда имеет проблемы с последним запросом, так что вы можете запускать утверждения против него, как

lastRequest.headers should contain(`Accept-Encoding`(ContentCoding.gzip))

Этот подход очень хорошо работает для нас, и мы можем проверить, что наши клиенты обрабатывают все виды выходных данных с серверов, а также все манипуляции с запросом, который они делают

...