Spring Retry Junit throws Checked exception is invalid for this method! is thrown

46 Views Asked by At

I'm trying to write a unit test for resttemplate.getForEntity() method and trying to cover Exception.class catch block.

Below is the code for same:

import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate;


@Service
@Slf4j
public class ProductService {
    
    
    private String productInfoBaseUrl = "http://localhost";

    @Value("${service.product.metadata}")
    private String productInfoPathProductMetadata;

    private static final Logger logger = LoggerFactory.getLogger(ProductService.class);

    @Autowired
    RestTemplate restTemplate;


    @Retryable(retryFor = {RuntimeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 10000))
    public ProductResponse getProductMetadata(String sku, HandlerResult handlerResult) {
        
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
        String errorMsg;

        String endpoint = getProductFetchMetadataEndpoint();

        ResponseEntity<String> response;

        try {
            response = restTemplate.getForEntity(endpoint, String.class);
        } catch (RuntimeException ex) {
            errorMsg = "getProductMetadata threw exception: " + ex.getMessage();
            HandlerResult.updateStatus(handlerResult, errorMsg, false);
            throw ex;
        }
         catch (Exception ex) {
            errorMsg = "getProductMetadata threw exception: " + ex.getMessage();
            log.info(errorMsg);
            HandlerResult.updateStatus(handlerResult, errorMsg, false);
            return null;
        }

        ProductResponse metadataResponse;
        Gson gson = new Gson();
        try {
            metadataResponse = gson.fromJson(response.getBody(), ProductResponse.class);
        } catch (Exception e) {
            errorMsg = String.format("Exception ProductResponse converting %s to map", response.getBody());
            log.info(errorMsg);
            HandlerResult.updateStatus(handlerResult, errorMsg, false);
            return null;
        }

        
        return metadataResponse;
    }

    private String getProductFetchMetadataEndpoint() {
        return productInfoBaseUrl + "/" + productInfoPathProductMetadata + "/";
    }
}

Below is the Unit Test Code:

@ExtendWith(MockitoExtension.class)
public class ProductServiceTest {

    @InjectMocks
    ProductService productService;

    //@Mock
    @Spy
    RestTemplate restTemplate;

    @Test
    public void testGetProductMetadataException() throws Exception {
        Mockito.doThrow(RestClientException.class).when(restTemplate).getForEntity(any(), any());
        ProductMetadataResponse result = productService.getProductMetadata("NX35100", null);
        Assertions.assertNull(result);
    }
}

    

I'm getting below error by running above unit test:

org.mockito.exceptions.base.MockitoException: 
Checked exception is invalid for this method!
Invalid: java.lang.Exception

    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:419)

Can someone please guide me on what I'm missing?

1

There are 1 best solutions below

0
Wey On

I think there are 2 problems here:

  1. You should use @Mock instead of @Spy as @Spy will just monitor the actual instance and the actual method will still be triggered when the method is invoked. In your use case, you want the exception to be thrown when getForEntity method is invoked.

  2. You should use when(xxx).thenThrow(yyy), not doThrow(yyy).when(xxx).getForEntity(...). The latter is designed for void return type. And getForEntity is not void method.