0

I have used Spring's retry in my project to achieve retry functionality for specific exceptions.

SomeService.java

@Service
@Slf4j
public class SomeService implements AuthenticationService {
    private static final String LOGIN_END_POINT = "/api/auth/v1/login";
    private static final String VALIDATE_TOKEN_END_POINT = "/api/auth/v1/validate";

    private RestTemplate restTemplate;
    private CdsConfig cdsConfig;

    @Autowired
    public SomeService(CdsConfig cdsConfig, RestTemplate restTemplate) {
        this.cdsConfig = cdsConfig;
        this.restTemplate = restTemplate;
    }

    @Retryable(
            value = {IOException.class, ResourceAccessException.class},
            maxAttemptsExpression = "${cds.retryMaxAttempts}",
            backoff = @Backoff(delayExpression = "${cds.retryMaxDelay}"))
    public boolean isValidToken(String jwtToken) {
        log.info("TRYING.......isValidToken");
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setBearerAuth(jwtToken);
        HttpEntity<Void> httpEntity = new HttpEntity<>(httpHeaders);
        ResponseEntity<Void> responseEntity =
                this.restTemplate.exchange(
                        cdsConfig.getHostUrl() + VALIDATE_TOKEN_END_POINT,
                        HttpMethod.GET,
                        httpEntity,
                        Void.class);
        return responseEntity.getStatusCode().equals(HttpStatus.OK);
    }

This code is working fine ie when another service is down then it is retrying for 3 times as expected. But while testing this method for retry counts, it is getting invoked only for 1 time.(expecting it to be 3 times.)

I wrote Junit for above method as following.

@ExtendWith(MockitoExtension.class)
@SpringBootTest
@EnableRetry
class AuthenticationServiceTest {

    AuthenticationService authenticationService;

    @Mock
    RestTemplateConfig restTemplateConfig;
    @Mock
    RestTemplateBuilder restTemplateBuilder;
    @Value("${cds.retryMaxAttempts}")
    int retryCount;
    ////////////////////////// various mocks for application context///////

    @Mock 
    private RestTemplate restTemplate;
    @Mock
    private CdsConfig cdsConfig;

    @BeforeEach
    void setUp() {
        this.authenticationService = new SomeService(cdsConfig, restTemplate, callerService);
        when(this.cdsConfig.getHostUrl()).thenReturn(URI.create("https://someUrl.com"));
        System.out.println("Retry count=" + retryCount);
    }
    @Test
    void test_isValidToken_whenAuthServiceDown_thenRetry() {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setBearerAuth("ValidJwtToken");
        HttpEntity<Void> httpEntity = new HttpEntity<>(httpHeaders);

        when(this.restTemplate.exchange(
                "--CorrectUrl--/api/auth/v1/validate",
                HttpMethod.GET,
                httpEntity,
                Void.class
        )).thenThrow(new ResourceAccessException("authentication service is down"));

        assertThrows(ResourceAccessException.class, () -> this.authenticationService.isValidToken("ValidJwtToken"));
        //verify(authenticationService, times(3)).isValidToken(anyString());
        verify(this.restTemplate, times(3))
                .exchange("https://dev.csm.osttra.com/api/auth/v1/validate", HttpMethod.GET, httpEntity, Void.class);

    }
}

This test is failing with below error.

Retry count=3 2023-05-09 19:30:38.968 INFO [abc-svc,,] 22624 --- [ main] c.o.cds.client.impl.PlatformAuthService : TRYING.......isValidToken

org.mockito.exceptions.verification.TooFewActualInvocations: restTemplate.exchange( "--correctBaseUrl--/api/auth/v1/validate", GET, <[Authorization:"Bearer ValidJwtToken"]>, class java.lang.Void ); Wanted 3 times: -> at com.osttra.cds.client.AuthenticationServiceTest.test_isValidToken_whenAuthServiceDown_thenRetry(AuthenticationServiceTest.java:213) But was 1 time: -> at com.osttra.cds.client.impl.SomeService.isValidToken(SomeService.java:74)

1 Answers1

0

this.authenticationService = new SomeService(...

You are creating the service in the test; for retry to work, the service must be created and managed by Spring.

Spring wraps the service in a proxy and adds an interceptor.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Hi Gary, Thank you for your answer. though what you are saying is correct, but this was not the real issue. I have used @Retryable in my code without any additional Retry config / Retry template ,retry policy. and hence while testing thouse configuration are not getting picked up. – Adarsh Gupta May 12 '23 at 05:23
  • Just for the sake of testing retry functionality, we can use a retyr template and execute our tests with it . It will work fine. But the ideal solution is to properly define Retry Template and policy in our project. – Adarsh Gupta May 12 '23 at 05:25
  • It **is** the real issue. The service **must** be a Spring bean. The framework creates the template and adds it to an AOP interceptor when it detects the annotation. – Gary Russell May 12 '23 at 14:06