mock rest template for unit test

2019-08-12 08:38发布

问题:

I want to mock a RestTemplate in Spring Boot, where I'm making a REST call within a method. To test the controller of the microservice I'm creating, I want to test methods inside the controller of my micro service.

For example:

@GetMapping(value = "/getMasterDataView", produces = { MediaType.APPLICATION_JSON_VALUE })
@CrossOrigin(origins = { "http://192.1**********" }, maxAge = 3000)
public ResponseEntity<MasterDataViewDTO> getMasterDataView() throws IOException {

    final String uri = "http://localhost:8089/*********";

    RestTemplate restTemplate = new RestTemplate();
    MasterDataViewDTO masterDataViewDTO = restTemplate.getForObject(uri, MasterDataViewDTO.class);

    return new ResponseEntity<>(masterDataViewDTO, HttpStatus.OK);

}

how to I test this using mocking?

This is what I have so far:

@Test
    public void testgetMasterDataView() throws IOException {

    MasterDataViewDTO masterDataViewDTO= mock(MasterDataViewDTO.class);
    //String uri = "http://localhost:8089/*********"; 

    Mockito.when(restTemplate.getForObject(Mockito.anyString(),ArgumentMatchers.any(Class.class))).thenReturn(masterDataViewDTO);

    assertEquals("OK",inquiryController.getMasterDataView().getStatusCode());        
}

I am getting an error when I'm running the mock, the method getMasterDataView() is getting called and the REST call within it is also getting called and is throwing an error. How can I write my test so that the REST endpoint is not called? If it's possible, I'd like to do this with Mockito.

回答1:

Before you start writing a test, you should change your code a bit. First of all, it would be a lot easier if you extracted that RestTemplate, and created a separate bean for it which you would inject within your controller.

To do that, add something like this within a @Configuration class or within your main class:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

Additionally, you have to remove the new RestTemplate() from your controller, and autowire it in stead, for example:

@Autowired
private RestTemplate restTemplate;

Now that you've done that, it's going to be a lot easier to inject a mock RestTemplate within your tests.

For your testing, you have two options:

  1. Either mock RestTemplate and all the methods you are trying to access, using a mocking framework (eg. Mockito)
  2. Or you can use MockRestServiceServer, which allows you to write tests that verify if the URLs are properly called, the request matches, and so on.

Testing with Mockito

To mock your RestTemplate with Mockito, you have to make sure that you add the following annotation to your tests:

@RunWith(MockitoJUnitRunner.class)

After that, you can do this:

@InjectMocks
private MyController controller;
@Mock
private RestTemplate restTemplate;

And now you can adjust your tests like this:

@Test
public void testgetMasterDataView() throws IOException {
    MasterDataViewDTO dto = new MasterDataViewDTO();
    when(restTemplate.getForObject("http://localhost:8089/*********", MasterDataViewDTO.class)).thenReturn(dto);
    ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(response.getBody()).isEqualTo(dto);
}

You could mock the DTO as you did within your test, but you don't have to, and I don't think there's any benefit from doing so. What you do have to mock is the restTemplate.getForObject(..) call.

Testing with MockRestServiceServer

Another approach is to use MockRestServiceServer. To do that, you have to use the following annotations for your test:

@RunWith(SpringRunner.class)
@RestClientTest

And then you'll have to autowire your controller and MockRestServiceServer, for example:

@Autowired
private MyController controller;
@Autowired
private MockRestServiceServer server;

And now you can write tests like this:

@Test
public void testgetMasterDataView() throws IOException {
    server
        .expect(once(), requestTo("http://localhost:8089/*********"))
        .andExpect(method(HttpMethod.GET))
        .andRespond(withSuccess(new ClassPathResource("my-mocked-result.json"), MediaType.APPLICATION_JSON));
    ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    // TODO: Write assertions to see if the DTO matches the JSON structure
}

In addition to testing that your actual REST call matches, this also allows you to test if your JSON-to-DTO works as well.



回答2:

You can achieve this by using @RestClientTest and MockRestServiceServer. An example provided in their documentation:

@RunWith(SpringRunner.class)
@RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

    @Test
    public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
            throws Exception {
        this.server.expect(requestTo("/greet/details"))
                .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
        String greeting = this.service.callRestService();
        assertThat(greeting).isEqualTo("hello");
    }

}