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