Resilience4j RetryConfig.custom() not working

79 Views Asked by At

I am trying to implement a test with a RetryConfig. Although I say which exception should be retried, nothing happens. The error is thrown and the test is finished. Fallback-method is also not trigered. Whats wrong with my test?

TestConfig.java

@TestConfiguration
public class TestRetryConfig {

@Autowired
private RetryRegistry retryRegistry;

@Bean("FeignRetry")
public Retry retryWithCustomConfig() {
    RetryConfig customConfig = RetryConfig.custom()
        .maxAttempts(3)
        .waitDuration(Duration.ofSeconds(1))
        .retryExceptions(FeignException.class)
        .build();
    return retryRegistry.retry("feignException", customConfig);
}
}

TestClass.java

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MyAdapter.class, InMemoryRetryRegistry.class, ServerInternalErrorVerifier.class, TestRetryConfig.class})
class MyAdapterRetryTest {

@Autowired
private RetryRegistry registry;

@Autowired
private MyAdapter myAdapter;

@Autowired
private Retry retry; //i see here, that my retry has been injected correctly with defined bean-name

@MockBean
private MyClient myClient;

@Test

    Request feignRequest =
        Request.create(Request.HttpMethod.POST, "", Map.of(), "".getBytes(StandardCharsets.UTF_8), null, null);
    FeignException.InternalServerError feignException =
        new FeignException.InternalServerError("", feignRequest, "".getBytes(StandardCharsets.UTF_8), null);

    when(myClient.sendPerson(any())).thenThrow(feignException); //here should retry be trigered


    String result = myAdapter.sendPerson(myRequest);
    assertThat(result).contains("was not send successfully");

}

}

My Prod-Method

@Retry(name = "throwingFeignException", fallbackMethod = "logCommunicationFailure")
    public String sendPartner(MyRequest myRequest) {
        MyResponse response = myClient.sendPerson(myRequest);
        return "ok";
    }


    private String logCommunicationFailure(MyRequest myRequest, Exception exception) { 
        return "fail";
    }
1

There are 1 best solutions below

1
Tamas Csizmadia On BEST ANSWER

TL;DR:

The short answer is: you need to call Retry.decorateFunction() with your Retry retry object and the sendPartner() method from the 'My Prod-Method' snippet you provided (there is an inconsistency, since you're referring to sendPerson() in your test -- I reckon you refer to the same method and it's just a typo.

assertThrows(
    FeignException.InternalServerError.class, 
    () -> Retry.decorateFunction(retry, feignService::sendPartner).apply(person));
verify(myClient, times(3)).sendPerson(any(Person.class));

This will execute your sendPartner() method decorated with @Retry and verifies it was retried 3 times as you configured.

Long Answer: A Working Example

FeignRetryConfiguration Bean

@Configuration
public class FeignRetryConfiguration {

    @Bean
    MyClient myClient() {
        return Feign.builder()
            .client(new Client.Default(null, null))
            .encoder(new Encoder.Default())
            .decoder(new Decoder.Default())
            .target(MyClient.class, "http://localhost:8080");
    }

    @Bean
    RetryConfig feignRetryConfig() {
        return RetryConfig.custom()
                .maxAttempts(3)
                .waitDuration(Duration.ofSeconds(1))
                .retryExceptions(FeignException.InternalServerError.class)
                .build();
    }

    @Bean("feignRetry")
    RetryRegistry retryRegistry() {
        return RetryRegistry.of(feignRetryConfig());
    }

    @Bean
    Retry retryFeign(@Autowired @Qualifier("feignRetry") RetryRegistry retryRegistry) {
        return retryRegistry.retry("throwingFeignException");
    }
}

FeignService Bean

@Service
public class FeignService {

    MyClient myClient;

    public FeignService(MyClient myClient) {
        this.myClient = myClient;
    }

    @Retry(name = "throwingFeignException", fallbackMethod = "logCommunicationFailure")
    public String sendPartner(Person person) {
        myClient.sendPerson(person);
        return "ok";
    }


    public String logCommunicationFailure(Person person, Exception exception) {
        return "fail";
    }
}

The MyClient

public interface MyClient {

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void sendPerson(Person person);
}

Person is a POJO - skipping for brevity

The actual unit test

@SpringBootTest
class MyClientTest {

    @TestConfiguration
    static class MyClientTestConfiguration {
        @Bean
        FeignService feignService(@Autowired  MyClient myClient) {
            return new FeignService(myClient);
        }
    }

    @MockBean
    MyClient myClient;

    @Autowired
    @Qualifier("retryFeign")
    Retry retry;

    @Autowired
    FeignService feignService;

    @Test
    void testSendPartnerShouldRetry() {
        Request request = Request.create(Request.HttpMethod.POST, "", Collections.emptyMap(), "".getBytes(StandardCharsets.UTF_8), null);

        doThrow(new FeignException.InternalServerError("internal error", request, "".getBytes(StandardCharsets.UTF_8), Collections.emptyMap()))
            .when(myClient).sendPerson(any(Person.class));

        Person person = new Person();
        person.setName("John Doe");
        person.setEmail("[email protected]");
        person.setPhone("123456789");
        assertThrows(FeignException.InternalServerError.class, () -> Retry.decorateFunction(retry, feignService::sendPartner).apply(person));
        verify(myClient, times(3)).sendPerson(any(Person.class));
    }
}

Certainly you need to extend this code to make it work as expected, but hopefully it will demonstrate how to make it happen.