creas443
creas443

Reputation: 21

problem mocking restTemplate with mockMvc

I need to write a unit test for a rest controller endpoint that calls a service which calls RestTemplate exchange()

@RestController
public class MyController {

    @Autowired
    Myservice myservice;

    @GetMapping("todo")
    public ResponseEntity<String> getTodo() {
        String todoJson = myservice.getTodo();
        return ResponseEntity.ok(todoJson);
    }
}

Here's my service class

@Service
public class Myservice {

    public String  getTodo() {
        ResponseEntity<Todo> response = restTemplate.exchange("https://jsonplaceholder.typicode.com/todos/1", HttpMethod.GET, null, Todo.class);
        Todo todo = response.getBody();
        return objectMapper.writeValueAsString(todo);
    }
}

And test case

@ExtendWith(SpringExtension.class)
class MyControllerTestJunit5 {

    private MockMvc mockMvc;

    @Mock
    private RestTemplate restTemplate;

    @InjectMocks
    MyController myController;

    @Mock
    Myservice myservice;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
    }

    @Test
    public void mockResttemplate() throws Exception {
        Todo todosample = new Todo(5,6,"myfield", true);

        ResponseEntity<Todo> responseEntity  = ResponseEntity.ok(todosample);

        when(restTemplate.exchange(
                ArgumentMatchers.anyString(),
                any(HttpMethod.class),
                ArgumentMatchers.any(),
                ArgumentMatchers.eq(Todo.class)))
                .thenReturn(responseEntity);

        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
                .andExpect(status().isOk())
                .andReturn();
        String myresult = result.getResponse().getContentAsString();
        System.out.println("response: " + myresult);
    }

printing the response at the end shows that the response is empty. How can I get this to work?

Upvotes: 0

Views: 492

Answers (1)

Dmitry Khamitov
Dmitry Khamitov

Reputation: 3296

I assume that you expect to get the JSON representation of todosample object. The problem in the test is that you are mocking Myservice by annotating it with @Mock. That mocked service is used by MyController because it's annotated with @InjectMocks. As a result, when you make a call to the /todo endpoint the controller calls myservice.getTodo() which returns null as the Myservice mock is not configured to return anything else. After that, the null value gets propagated to the ResponseEntity body which materializes to the empty OK response in your test.

I believe a better approach for this kind of test will be to just mock Myservice and configure it properly. The first part is done already as I mentioned. The configuration is easy. You should just replace restTemplate.exchange call configuration with the myservice.getTodo() one. Something like this should work:

@Test
public void mockResttemplate() throws Exception {
    String todosampleJson = "{}"; // just empty json

    when(myservice.getTodo()).thenReturn(todosampleJson);

    MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
            .andExpect(status().isOk())
            .andReturn();
    String myresult = result.getResponse().getContentAsString();
    System.out.println("response: " + myresult); // will print "{}" now
}

And then you can have a separate test for Myservice where you have to mock restTemplate.exchange call.

Of course technically, you can still get away with mocking the restTemplate in your MyController test but then you have to make sure you instantiate the real Myservice instead of the mocked version of it. However, in this case you would expose the implementation details of Myservice to the MyController class, which kinda contradicts the MVC pattern.

Upvotes: 1

Related Questions