javajunit5retry-logicresilience4j

Resilience4j RetryConfig.custom() not working


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";
    }

Solution

  • 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("john@doe.com");
            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.