How to test Spring WebClient retry when?
Asked Answered
H

3

6

I need to implement following behavior:

  • Make a REST post request
  • If response returns with a status 429 Too many requests, retry up to 3 times with a delay of 1 second
  • If the third retry fails or any other error occurs, log and write something to the database
  • If the request was successful (http status 200), log some information

I would like to use Spring WebClient for this purpose and came up with this code:

Mono<ClientResponse> response = webClient.post()
            .uri(URI.create("/myuri"))
            .body(BodyInserters.fromObject(request))
            .retrieve()
            .onStatus(httpStatus -> httpStatus.equals(HttpStatus.TOO_MANY_REQUESTS), 
                      response -> Mono.error(new TooManyRequestsException("System is overloaded")))
            .bodyToMono(ClientResponse.class)
            .retryWhen(Retry.anyOf(TooManyRequestsException.class)
                                          .fixedBackoff(Duration.ofSeconds(1)).retryMax(3))
            .doOnError(throwable -> saveToDB(some_id, throwable))
            .subscribe(response -> logResponse(some_id, response));

Now I would like to test if the retry mechanism and error handling works as I expect. May be I could use StepVerifier for this purpose, but I just cannot figure out how to use it in my case. Any useful hints?

Hutchinson answered 16/9, 2019 at 14:42 Comment(0)
N
8

I think that you might be able to test this with a mock web server, e.g. MockWebServer.

@Test
public void testReactiveWebClient() throws IOException
{
    MockWebServer mockWebServer = new MockWebServer();

    String expectedResponse = "expect that it works";
    mockWebServer.enqueue(new MockResponse().setResponseCode(429));
    mockWebServer.enqueue(new MockResponse().setResponseCode(429));
    mockWebServer.enqueue(new MockResponse().setResponseCode(429));
    mockWebServer.enqueue(new MockResponse().setResponseCode(200)
                                  .setBody(expectedResponse));

    mockWebServer.start();

    HttpUrl url = mockWebServer.url("/mvuri");
    WebClient webClient = WebClient.create();

    Mono<String> responseMono = webClient.post()
            .uri(url.uri())
            .body(BodyInserters.fromObject("myRequest"))
            .retrieve()
            .onStatus(
                    httpStatus -> httpStatus.equals(HttpStatus.TOO_MANY_REQUESTS),
                    response -> Mono.error(new TestStuff.TooManyRequestsException("System is overloaded")))
            .bodyToMono(String.class)
            .retryWhen(Retry.anyOf(TestStuff.TooManyRequestsException.class)
                               .fixedBackoff(Duration.ofSeconds(1)).retryMax(3));

    StepVerifier.create(responseMono)
            .expectNext(expectedResponse)
            .expectComplete().verify();

    mockWebServer.shutdown();
}

If you enqueue another MockResponse with a statuscode 429, the verification will fail, same with e.g. errorcode 500.

Norse answered 16/9, 2019 at 20:57 Comment(0)
H
3

Dominik Sandjaja's answer basically helped me to set up my test. Just for sake of completeness I post here the working version:

@Test
public void mytest() {
    MockResponse mockResponse = new MockResponse()
        .setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
        .setResponseCode(429).setBody("Too many requests");
    mockWebServer.enqueue(mockResponse);
    mockWebServer.enqueue(mockResponse);
    mockWebServer.enqueue(mockResponse);
    mockWebServer.enqueue(mockResponse);

    Mono<MyClass> responseMono = methodDoingTheAsyncCall.sendAsync(...);
    StepVerifier.create(responseMono)
                .expectError(RetryExhaustedException.class)
                .verify();
}
Hutchinson answered 2/10, 2019 at 6:50 Comment(0)
T
0

For the testing retries you can use also WireMock Scenarios.

@Test
public void test() {
    wireMockServer.stubFor(put(urlEqualTo(URI)).inScenario("Retry")
            .willReturn(aResponse()
                .withStatus(HttpStatus.TOO_MANY_REQUESTS.value())
                .withHeader(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE)))
            .setNewScenarioState("Cause Success");

    wireMockServer.stubFor(put(urlEqualTo(URI)).inScenario("Retry")
        .whenScenarioStateIs("Cause Success")
        .willReturn(aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE)));

    try {
        service.makeWebClientCall();
    } catch (final TooManyRequestsException e) {
        // expected
    }

    wireMockServer.verify(2, putRequestedFor(urlPathMatching(URI)));
}
Tab answered 18/10 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.