JAVA mockito unit test for resttemplate and retryTemplate
Asked Answered
C

4

10

I am currently writing unit test for below method

@Autowired
private RequestConfig requestConfig;

@Autowired
private RetryTemplate retryTemplate;

public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
    try {
        return retryTemplate.execute(retryContext -> {

            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = requestConfig.createHttpHeaders();
            HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
            ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
            return response;

        });
    } catch (Exception e) {
        throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
    }
}

UPDATED METHOD:

@Autowired
private RequestConfig requestConfig;

@Autowired
private RetryTemplate retryTemplate;

@Autowired
private RestTemplate restTemplate;

public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
    try {
        return retryTemplate.execute(retryContext -> {

            HttpHeaders headers = requestConfig.createHttpHeaders();
            HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
            ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
            return response;

        });
    } catch (Exception e) {
        throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
    }
}

I tried all possibilities but I am unable to get it right. Here is my below test.

@Mock
private RestTemplate restTemplate;

@Mock
public RequestConfig requestConfig;

@InjectMocks
private RetryTemplate retryTemplate;

ServiceRequest serviceRequest;


@Test
public void makeGetServiceCall() throws Exception {
    String url = "http://localhost:8080";
    RetryTemplate mockRetryTemplate = Mockito.mock(RetryTemplate.class);
    RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class);
    ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
    Mockito.when(mockRetryTemplate.execute(ArgumentMatchers.any(RetryCallback.class), ArgumentMatchers.any(RecoveryCallback.class), ArgumentMatchers.any(RetryState.class))).thenReturn(myEntity);

    Mockito.when(mockRestTemplate.exchange(
            ArgumentMatchers.eq(url),
            ArgumentMatchers.eq(HttpMethod.GET),
            ArgumentMatchers.<HttpEntity<String>>any(),
            ArgumentMatchers.<Class<String>>any())
    ).thenReturn(myEntity);

    ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
    Assert.assertEquals(myEntity, response);
}

UPDATED TEST CASE:

 @Mock
public RequestConfig requestConfig;

@Mock
private RestTemplate restTemplate;

@Mock
private RetryTemplate retryTemplate;

@InjectMocks
ServiceRequest serviceRequest;

@Test
public void makeGetServiceCall() throws Exception {
    //given:
    String url = "http://localhost:8080";

    when(requestConfig.createHttpHeaders()).thenReturn(null);
    ResponseEntity<String> myEntity = new ResponseEntity<>( HttpStatus.ACCEPTED);
    when(retryTemplate.execute(any(RetryCallback.class), any(RecoveryCallback.class), any(RetryState.class))).thenAnswer(invocation -> {
        RetryCallback retry = invocation.getArgument(0);
        return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
    });
    when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(String.class)))
            .thenReturn(myEntity);

    //when:
    ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);

    //then:
    assertEquals(myEntity, response);
}

The response object which I get from my method call makeGetServiceCall always return null. When I debug the code I see exception org.mockito.exceptions.misusing.WrongTypeOfReturnValue: ResponseEntity cannot be returned by toString() toString() should return String error on the resttemplate mocking where I return myEntity

I am not sure what am I missing.

Cher answered 27/2, 2019 at 22:16 Comment(3)
Why do you mock RetryTemplate.execute(RetryCallback, RecoveryCallback, RetryState) again??? That's why you get null as a result of calling RetryTemplate.execute(RetryCallback) in your ServiceRequest. Now if you replace 3-arg call with 1-arg call as in my answer, then you will get myEntity in response. Also, you can always check which method is called by adding verify(retryTemplate, times(1)).execute(any(RetryCallback.class)); before assertion... and it'll succeed. Or if you add verification with 3 args then it will fail...Weinert
Thanks, I think that was the error. It works now.Cher
Since you are only providing a callback in your service, mock is going to look for the other 2 parameters in the execute method as well (look at the source code for RetryTemplate). Use the following to make it work. when(retryTemplate.execute(any(RetryCallback.class), isNull(), isNull()))...Undercut
W
10

Well, you have made quite some number of mistakes...

  1. I'm sure you wanted to annotate private RetryTemplate retryTemplate; with @Mock, not @InjectMocks
  2. @InjectMocks should go onto ServiceRequest serviceRequest;
  3. You are defining interactions on some mockRetryTemplate and mockRestTemplate which have nothing to do with serviceRequest. Instead, you should use your @Mock-annotated fields to define interactions on because they are being injected into your object under test (serviceRequest)
  4. Moreover, you can't normally mock RestTemplate and inject it into your ServiceRequest because you don't use dependency injection in the first place for RestTemplate in ServiceRequest. You just instantiate its instance in ServiceRequest.makeGetServiceCall
  5. You are defining an interaction on the wrong method at line Mockito.when(retryTemplate.execute(.... Your interaction specifies RetryTemplate.execute(RetryCallback, RecoveryCallback, RetryState) whereas your ServiceRequest uses another method RetryTemplate.execute(RetryCallback)
  6. You should also notice that RetryTemplate.execute is final and so you can't mock it without extra efforts as explained here. And generally, you should prefer interfaces over classes, e.g. RestOperations and RetryOperations over RestTemplate and RetryTemplate respectively, to be more flexible.

That said, below is the working test which solves your problem. But take note of removing RestTemplate restTemplate = new RestTemplate(); from ServiceRequest and making restTemplate a field so it's dependency-injected.

@RunWith(MockitoJUnitRunner.class)
public class ServiceRequestTest {
    @Mock
    private RestTemplate restTemplate;

    @Mock
    public RequestConfig requestConfig;

    @Mock
    private RetryTemplate retryTemplate;

    @InjectMocks
    ServiceRequest serviceRequest;

    @Test
    public void makeGetServiceCall() throws Exception {
        //given:
        String url = "http://localhost:8080";
        ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
        when(retryTemplate.execute(any(RetryCallback.class))).thenAnswer(invocation -> {
            RetryCallback retry = invocation.getArgument(0);
            return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
        });
        when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
                .thenReturn(myEntity);

        //when:
        ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);

        //then:
        assertEquals(myEntity, response);
    }
}
Weinert answered 2/3, 2019 at 20:44 Comment(11)
Thanks for sharing. But I am getting error for : org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers! 3 matchers expected, 1 recorded: on the parameters passed to the restTemplate exchangeCher
@Cher have you tried my exact example? Looks like you are mixing matchers and values in restTemplate interaction, aren’t you? In my example I use only matchersWeinert
Yes, seems that we need to add 3 parameters, retryTemplate.execute(any(RetryCallback.class), any(RecoveryCallback.class), any(RetryState.class) something to with version of mockito I guess, got it from here https://mcmap.net/q/1162958/-mocking-anonymous-function . But now I am getting assertion error, which I think makes sens as retry template will return null. But I am not sure how would I mock it to get what I expect.Cher
@Cher which version of Mockito do you use? Have you checked my 6th point about mocking final methods? If you don't configure this, it won't be mocked and you will get assertion error. It had happened to me before I configured mock-maker-inlineWeinert
I use version 2.8.47 of mockito, I was thinking that version 2+ would mock the final.Cher
No, it doesn't unless you activate it. Check the link from my point 6Weinert
I did these changes and added the mock maker inline testImplementation 'org.mockito:mockito-inline:2.13.0' as well, but I get null response, i am expecting it to be a ResponseEntity object. Is it because the context is set to null in RetryTemplate mock?Cher
No you are getting null not because of context being null. Did you remove RestTemplate restTemplate = new RestTemplate(); from ServiceRequest and add @Autowired private RestTemplate restTemplate; to this class instead? So restTemplate is mocked as you expect. As I recommended in the 4thWeinert
I have updated the method and test in question. I have added the RestTemplate as dependency using AutowiredCher
@Cher Did injecting the RestTemplate work. I am experiencing a similar thing where the RetryTemplate returns null regardless of what I doRaisaraise
It worked. But instead of null in retry.doWithRetry() I had to create new RetryContext in my case because the code uses context.Erbium
C
7

For me below worked, otherwise, it was returning null always

when(retryTemplate.execute(any(),any(),any())).thenAnswer(invocation -> {
        RetryCallback retry = invocation.getArgument(0);
        return retry.doWithRetry(null);
});

and import was import static org.mockito.ArgumentMatchers.any;

Chauffer answered 14/7, 2020 at 13:23 Comment(0)
C
0

Generic solution:

Mockito.when(retryTemplate.execute(Matchers.any(),Matchers.any(),Matchers.any())).thenAnswer(invocation -> {
            RetryCallback retry = invocation.getArgumentAt(0,Matchers.any());
            return retry.doWithRetry(null);
        });
Clinkerbuilt answered 22/9, 2020 at 14:37 Comment(0)
K
0

It works for me!

@ExtendWith(MockitoExtension.class)
class RetryableRestClientTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private RetryTemplate retryTemplate;

    @InjectMocks
    private RetryableRestClient client;

    @SuppressWarnings("rawtypes")
    @Test
    void test_get() {

        String url = "https://faked-url";

        ResponseEntity<String> expectedResponseEntity = new ResponseEntity<>(HttpStatus.OK);

        Mockito.when(retryTemplate.execute(Mockito.any(), Mockito.any(), Mockito.any()))
                .thenAnswer(invocation -> {
                    RetryCallback retry = invocation.getArgument(0);
                    return retry.doWithRetry(null);
                });

        Mockito.when(restTemplate.exchange(Mockito.eq(url), Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(String.class)))
                .thenReturn(expectedResponseEntity);

        ResponseEntity<String> actualResponseEntity = client.get(url);

        Assertions.assertEquals(expectedResponseEntity, actualResponseEntity);

    }
}

@Component
public class RetryableRestClient {

    @Autowired
    private RetryTemplate retryTemplate;

    @Autowired
    private RestTemplate restTemplate;

    private HttpHeaders fakeHttpHeaders() {
        HttpHeaders headers = new HttpHeaders();
        // fake browser's behavior
        headers.add("authority", "m.nowscore.com");
        headers.add("cache-control", "max-age=0");
        headers.add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"");
        headers.add("sec-ch-ua-mobile", "?0");
        headers.add("upgrade-insecure-requests", "1");
        headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36");
        headers.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
        headers.add("sec-fetch-site", "none");
        headers.add("sec-fetch-mode", "navigate");
        headers.add("sec-fetch-user", "?1");
        headers.add("sec-fetch-dest", "document");
        headers.add("accept-language", "en-US,en;q=0.9");
        return headers;
    }

    public final ResponseEntity<String> get(String url) {
        return retryTemplate.execute(context -> restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, fakeHttpHeaders()), String.class));
    }
}

Keelson answered 14/8, 2021 at 15:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.