0

I am missing something in retry mechanism of HttpAsyncClient. I'm trying to test the retry strategy using Mockito and Mock Server.

I've created a simple snippet of code to test it with a client using the DefaultHttpRequestRetryStrategy with 4 retries.

    val httpClient: CloseableHttpAsyncClient = HttpAsyncClients.custom()
      .setRetryStrategy(new DefaultHttpRequestRetryStrategy(4, TimeValue.ofMilliseconds(500)))
      .build()

    httpClient.start()

Then I've initialized a mock server that always returns 429 status code. My test calls such server.

    val request: SimpleHttpRequest = SimpleRequestBuilder
      .put(baseUrl)
      .setPath("/test")
      .setBody("{}", ContentType.APPLICATION_JSON) // Adding this line test fails
      .build()
    httpClient.execute(request, new FutureCallback[SimpleHttpResponse] {

      override def completed(result: SimpleHttpResponse): Unit = {
        print("Process completed")
      }

      override def failed(ex: Exception): Unit = {
        print("Process failed")
      }

      override def cancelled(): Unit = {
        print("Cancelled")
      }
    })

What the test verifies is that the mock server is called 5 times.

      mockServer.verify(request().withPath("/test"), VerificationTimes.exactly(5))

The test succeeds if I send a request without body. In other cases, like the one in this example, it seems that the client doesn't call the server. So the test fails.

Am I missing something in this test? Can somebody help?

For completeness I attach the full code of this example.

package ai.faire.platform.kpi.engine.common

import ai.faire.platform.kpi.common.BaseSpec
import org.apache.hc.client5.http.async.methods.{SimpleHttpRequest, SimpleHttpResponse, SimpleRequestBuilder}
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy
import org.apache.hc.client5.http.impl.async.{CloseableHttpAsyncClient, HttpAsyncClients}
import org.apache.hc.core5.concurrent.FutureCallback
import org.apache.hc.core5.http.{ContentType, HttpStatus}
import org.apache.hc.core5.util.TimeValue
import org.mockserver.integration.ClientAndServer
import org.mockserver.integration.ClientAndServer.startClientAndServer
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.mockserver.verify.VerificationTimes

import java.util.concurrent.TimeUnit

case class HttpTestClient(baseUrl: String, httpClient: CloseableHttpAsyncClient) {

  def executeCall(): Unit = {
    val request: SimpleHttpRequest = SimpleRequestBuilder
      .put(baseUrl)
      .setPath("/test")
      .setBody("{}", ContentType.APPLICATION_JSON)
      .build()
    httpClient.execute(request, new FutureCallback[SimpleHttpResponse] {

      override def completed(result: SimpleHttpResponse): Unit = {
        print("Process completed")
      }

      override def failed(ex: Exception): Unit = {
        print("Process failed")
      }

      override def cancelled(): Unit = {
        print("Cancelled")
      }
    })
  }
}

class HttpClientSpec extends BaseSpec {

  var mockServer: ClientAndServer = _

  before {
    mockServer = startClientAndServer(8081)
  }

  "HTTP client" should "retry request in case of errors" in {
    // Setup /test endpoint that fails
    setupTestEndpointFailingWithStatus(HttpStatus.SC_TOO_MANY_REQUESTS)

    val httpClient: CloseableHttpAsyncClient = HttpAsyncClients.custom()
      .setRetryStrategy(new DefaultHttpRequestRetryStrategy(4, TimeValue.ofMilliseconds(500)))
      .build()

    httpClient.start()

    val httpTestClient = HttpTestClient("http://localhost:8081", httpClient)
    httpTestClient.executeCall()

    eventually {
      mockServer.verify(request().withPath("/test"), VerificationTimes.exactly(5))
    }
  }

  def setupTestEndpointFailingWithStatus(statusCode: Integer): Unit = {
    mockServer
      .openUI(TimeUnit.SECONDS, 2)
      .when(request().withPath("/test"))
      .respond(response().withStatusCode(statusCode))
  }
}
Gilberto T.
  • 358
  • 6
  • 19
  • I'm not familiar with mockserver `verify` but is it capable of verifying partial requests or should the request used for verification be the exact same one used in the real request (that is with a body)? – Gaël J Nov 23 '21 at 18:25
  • @GaëlJ `verify` is capable to verify also partial requests. In this specific case I want to verify that endpoint `/test` is called 5 times (original request and 4 retries). It's strange that when I add the body the client seems not calling the mock server. – Gilberto T. Nov 23 '21 at 23:40

0 Answers0