0

Relevant Code Below:

ServiceCode:

@Override
public ResponseEntity<AppointmentResponse> createAppointment(AppointmentRequest partnerFulfillmentRequest) {

    RestTemplate rt                                 = null;
    ResponseEntity<AppointmentResponse> response    = null;
    String uri                                      = null;
    HttpEntity<AppointmentRequest> httpEntity       = null;
    HttpHeaders headers = null;
    try {
            rt = new RestTemplate();
            rt.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            rt.getMessageConverters().add(new StringHttpMessageConverter());
            uri = new String(internalServiceUrl+"/"+APP_NAME_INTERNAL+"/appointment");
            log.info("Calling internal service URL : "+uri);
            headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            httpEntity = new HttpEntity<AppointmentRequest>(partnerFulfillmentRequest, headers);  
            response =  rt.exchange(uri, HttpMethod.PUT, httpEntity, AppointmentResponse.class);
            if (response != null)
            {
                log.info("Got response from internal servicec-->statusCode: "+response.getStatusCodeValue());
                log.info("Got response from internal service--> Body "+response.getBody());
            }


    }catch(HttpClientErrorException hceEx) {
        //hceEx.printStackTrace();
        AppointmentResponse res = new AppointmentResponse();
        return new ResponseEntity<AppointmentResponse>(mapResponse(hceEx.getResponseBodyAsString()), hceEx.getResponseHeaders(), hceEx.getStatusCode());
    }catch(Exception e) {
        e.printStackTrace();
        AppointmentResponse res = new AppointmentResponse();
        ResponseEntity<AppointmentResponse> wfmErrResponse = new ResponseEntity<AppointmentResponse>(res, HttpStatus.INTERNAL_SERVER_ERROR);
        log.error("ERROR WHILE CALLING INTERNAL SERVICE");
        log.error(uri);
        log.error(e);
        return wfmErrResponse;
    }
    return response;

}

Test Code:

@RunWith(MockitoJUnitRunner.class)
public class PartnerFulfillmentServiceImplTest {

@Mock
RestTemplate restTemplate;

@Mock
HttpHeaders httpHeaders;

@Mock
ResponseEntity responseEntity;

@InjectMocks
PartnerFulfillmentServiceImpl partnerFulfillmentService;

@Test
public void createAppointmentTest() {

    Whitebox.setInternalState(partnerFulfillmentService, "internalServiceUrl", "http://localhost:8080");

    AppointmentRequest appointmentRequest = new AppointmentRequest();
    appointmentRequest.setPartnerName("CENTRICITY");
    appointmentRequest.setTicketNumber("123ABC");

    httpHeaders = new HttpHeaders();
    httpHeaders.set("Content-type", "application/json");

    responseEntity = new ResponseEntity<>(
            "some response body",
            HttpStatus.OK
    );

    when(restTemplate.exchange(Mockito.anyString(),
            Mockito.<HttpMethod> any(),
            Mockito.<HttpEntity<?>> any(),
            Mockito.<Class<Object>> any()))
            .thenReturn(responseEntity);

    ResponseEntity<AppointmentResponse> response = partnerFulfillmentService.createAppointment(appointmentRequest);

    Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
}
}

I'm getting java.lang.AssertionError: Expected :500 Actual :200 and understandably so because it is not actually calling running .thenReturn(responseEntity); logic. My million dollar question is, why? It should be returning the responseEntity value. I have all arguments for the exchange() to any() in hopes to trigger the condition as often as possible as I can always narrow the conditions at a different time. Am I not mocking my restTemplate correctly? That is my current suspicion as to what is going on. Any advice would help!

Thanks!

Billy
  • 1,049
  • 3
  • 14
  • 23
  • 1
    Your test creates and stubs a mock restTemplate. But your method doesn't use that mocked RestTemplate. It creates its own new RestTemplate and uses it. – JB Nizet Jul 17 '19 at 22:00

1 Answers1

1

Like @JB Nizet pointed out, you are creating a new instance of RestTemplate inside your tested method. This means that the exchange method will be called from the new instance, and not a mock. You could implement it the way you did if the class that contains the method createAppointment had a dependency injection of RestTemplate.

What you want there, is to mock the constructor of the new instance of RestTemplate so that, when a new instance would be created, it will be substituted. Unfortunately, Mockito is not capable of mocking a constructor, so you should use PowerMockito for mocking constructors.

whenNew(RestTemplate.class).withNoArguments().thenReturn(restTemplate);

responseEntity = new ResponseEntity<>(
            "some response body",
            HttpStatus.OK
    );

when(restTemplate.exchange(Mockito.anyString(),
        Mockito.<HttpMethod> any(),
        Mockito.<HttpEntity<?>> any(),
        Mockito.<Class<Object>> any()))
        .thenReturn(responseEntity);
Gabriel Robaina
  • 709
  • 9
  • 24
  • Awesome! I’m not in the office but I will try this first thing tomorrow morning! Thanks Gabriel! – Billy Jul 18 '19 at 01:58
  • 1
    Alternatively - if you can not or do not want to use PowerMockito - you could move the creation of the RestTemplte to a different method, and replace this functionality while using a `spy` instead. – second Jul 18 '19 at 07:10
  • That is a great suggestion @second. It all comes down to deciding between encapsulating the new instance on a different method, so you can spy it, or adding a new dependency. – Gabriel Robaina Jul 18 '19 at 10:41
  • Had to do a small refactor for add a constructor and inject the restTemplate. Worked like a charm! Thank you to @GabrielPimenta and JB Nizet! – Billy Jul 18 '19 at 14:11