0

I have looked at multiple posts here - one for example and tried multiple variations but still can't seem to be able to get a non-null value after mocking
RestTemplate#exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables).

I am not passing any uriVariables.

Here is my simplified source code:

Request request = new Request();
request.setDataId(1);
String query = "query ($ request:Request!)\n" + 
            "{\n" + 
            "    fetchData(request:$request)\n" + 
            "    {\n" + 
            "        planId\n" + 
            "    }\n" +
            " }\n";

RequestBody requestBody = new RequestBody(query,Collections.singletonMap("request", 
            request));   
HttpEntity<RequestBody> request = new HttpEntity<>(requestBody, createHttpHeaders());   
return restTemplate.exchange("http://localhost:8080/gql", HttpMethod.POST, request, 
JsonNode.class);

private HttpHeaders createHttpHeaders()
{
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    return headers;
}

The RequestBody class (uses lombok):

@Getter
@AllArgsConstructor
public class RequestBody implements Serializable {
  private static final long serialVersionUID = -203524485553511477L;
  private String query;
  private Map<String,Object> variables;
}

And the corresponding test code:

 @Mock
 private RestTemplate restTemplate;

 @Mock
 private ObjectMapper objectMapper;
 
 @InjectMocks
 private FetchDataService fetchDataService;
  
 @Test
 public void testFetchData() {
  String json = new String(Files.readAllBytes(Paths.get("src/test/resources/Data.json")));
     ObjectMapper testMapper = new ObjectMapper();
     JsonNode expectedResponse = testMapper.readTree(json);
 when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), 
             any(Class.class)))
     .thenReturn(new ResponseEntity<>(expectedResponse, HttpStatus.OK));  
  ResponseEntity<JsonNode> response = fetchDataService.getData();   
  }

expectedResponse is a JsonNode instance above. The problem is that response always returns a null body.

I am able to verify that the call does happen if I use this -

  Mockito.verify(restTemplate)
     .exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class));

I'm using junit 5, spring boot 2.6.7, jdk 11 if that matters.

linuxNoob
  • 600
  • 2
  • 14
  • 30
  • Does your class `FetchDataService` has a constructor that take in parameter a RestTemplate object? – Harry Coder Sep 22 '22 at 07:19
  • @HarryCoder Yes, this is what it looks like `public FetchDataService(RestTemplate restTemplate)` – linuxNoob Sep 22 '22 at 17:01
  • 1
    Your simplified source code doesn't really help because you return the result of `restTemplate.exchange` from it, which is `ResponseEntity`. Whereas `fetchDataService.getData()` returns some `DataResponse`. As such, I can assume there is also some code between the `restTemplate.exchange` and `getData` calls. If you can just share that part it will be really helpful and the test misbehaviour will be easy to find. – Dmitry Khamitov Sep 24 '22 at 14:14
  • @DmitryKhamitov good catch. I have updated the source code to reflect that I'm directly returning `ResponseEntity` but the body of that entity is `null` and doesn't have the `JsonNode` object that I expect it to have. – linuxNoob Sep 25 '22 at 02:43
  • 1
    @linuxNoob given the `Mockito.verify(...)` doesn't throw and "the simplified" source code semantically and clearly represents the actual source code, I find it quite impossible for the `fetchDataService.getData()` to return `null`. Would you mind sharing the actual code of that method please? – Dmitry Khamitov Sep 25 '22 at 15:05
  • @DmitryKhamitov added some more details, I'm not sure what else to add. The actual query is big with nested fields but I have simplified it. Also, have verified using POSTMAN that it actually does return data. – linuxNoob Sep 26 '22 at 21:44
  • 1
    @linuxNoob can you also add the `expectedResponse` value then? In the question you are saying "it's a JsonNode instance above" but there is no any JsonNode instance above there... – Dmitry Khamitov Sep 26 '22 at 21:59
  • @DmitryKhamitov added. `Data.json` file just contains a sample json response and I can confirm that the `expectedResponse` is getting correctly populated. Also, I was incorrect/incomplete about the constructor earlier, this is what it looks like `public FetchDataService(RestTemplate restTemplate, ObjectMapper objectMapper)` – linuxNoob Sep 26 '22 at 22:47
  • 1
    @DmitryKhamitov you were spot on the fact that the issue was with post response processing piece. Specifically, after reading through the JSON the code does this `objectMapper.treeToValue(responseNode, DataResponse.class);` and since my test has a mock version of `ObjectMapper` without mocking `treeToValue()` it returns `null`. I appreciate you taking time to look into the problem, I was looking at the wrong place. If you could make an answer I'll accept it and mark it as solved. – linuxNoob Sep 26 '22 at 23:05

1 Answers1

1

As the author @linuxNoob has confirmed in our discussion in the comments, the issue was that expectedResponse didn't hold a proper JsonNode value. The reason for that is the ObjectMapper instance being mocked without any configuration to return something from its methods. So, any call was returning the default null value, i.e. testMapper.readTree(json) returns null that ends up being the value of the ResponseEntity.body through the expectedResponse.

Dmitry Khamitov
  • 3,061
  • 13
  • 21