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))
}
}