I am using @Retryable and @Recover, so when exception occurs during retry, it can be handled in different way. I use @EnableRetry in my configuration class where it instantiates my service bean. In my service interface class, I marked a method to @Retryable like this:
@Retryable(retryFor = DataIntegrityViolationException.class, maxAttempts = 2, backoff = @Backoff(delayExpression = "500"))
Lastly, I implemented @Recover method so it generates telemetry and throw e again:
@Recover
public CustomerMappingDto recoverDataIntegrityViolationException(DataIntegrityViolationException e, String customerId, Integer siteId, Integer siteLocationId, String siteLocationName) {
telemetryService.addGetCustomerMappingDefaultNpiCounter(customerId, siteId, siteLocationId, siteLocationName, AUTO_MAPPED_FAILED, false);
throw e;
}
I also created unit test (junit5) to cover cases where it retries like this:
@Test
public void getCustomerMappingAndUpdateRetryFailedWithDataIntegrityViolationException() {
CustomerMapping customerMapping = new CustomerMapping();
when(customerMappingRepository.findByCustomerIdAndSiteIdAndSiteLocationId(CUSTOMER_ID, SITE_ID, SITE_LOCATION_ID))
.thenReturn(null);
when(customerMappingRepository.findByCustomerIdAndSiteIdAndSiteLocationId(CUSTOMER_ID, SITE_ID, null))
.thenReturn(customerMapping);
doThrow(new DataIntegrityViolationException("Test Exception"))
.when(customerMappingRepository).save(any(CustomerMapping.class));
assertThrows(DataIntegrityViolationException.class, () -> customerMapperService.fetchCustomerMappingsAndUpdateAutoMapping(CUSTOMER_ID, SITE_ID, SITE_LOCATION_ID, SITE_LOCATION_NAME));
verify(customerMappingRepository, times(2)).save(any(CustomerMapping.class));
verify(customerMappingRepository, times(2)).findByCustomerIdAndSiteIdAndSiteLocationId(CUSTOMER_ID, SITE_ID, SITE_LOCATION_ID);
verify(customerMappingRepository, times(2)).findByCustomerIdAndSiteIdAndSiteLocationId(CUSTOMER_ID, SITE_ID, null);
verify(customerMappingRepository, never()).findByCustomerIdAndSiteId(CUSTOMER_ID, SITE_ID);
verify(telemetryService, times(1)).addGetCustomerMappingDefaultNpiCounter(CUSTOMER_ID, SITE_ID, SITE_LOCATION_ID, SITE_LOCATION_NAME, AUTO_MAPPED_FAILED, false);
}
I basically force method to throw DataIntegrityViolationException (exception that gets retried) each time, and make sure telemetryService
is called from @Recover method. I did debug and verified that it does go into @Recover method.
However, it is still not covered by code coverage in jacoco. As it is small project, code coverage goes down by 5% per each @Recover method. I tried calling it directly by creating my implementation class directly and call the recover method, but it wasn’t still covered.
assertThrows(DataIntegrityViolationException.class, () -> myServiceImpl.recoverDataIntegrityViolationException(...)
How can I unit test @Recover method(s) so it can be covered by jacoco code coverage?