Mocking RestTemplate # postForObject в модульном тесте - PullRequest
0 голосов
/ 09 мая 2018

Учитывая класс EncoderService, который имеет следующий метод createNewStream и набор констант, используемых в методе, как я могу использовать mockito для написания модульного теста для метода createNewStream:

public ResponseEntity<Object> createNewStream(Long channelId) {
    String url = IP + VERSION + serverName + VHOSTS + vhostName + APP_NAME + appName + STREAM_FILES;

    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    headers.setAcceptCharset(Arrays.asList(Charset.forName(UTF_8)));

    RestTemplate restTemplate = new RestTemplate();

    String udp = "udp://" + "localhost" + ":" + "1935";
    Map<String, String> map = new HashMap<>();
    map.put("name", STREAMS + appName + channelId);
    map.put("serverName", serverName);
    map.put("uri", udp);
    HttpEntity<Map<String, String>> request = new HttpEntity<>(map, headers);

    HttpStatus statusCode = null;
    try {
        ResponseEntity<Object> response = restTemplate.postForEntity(url, request, Object.class);
        statusCode = response.getStatusCode();
        map.put(MESSAGE, "successful");
        return new ResponseEntity<>(map, statusCode);
    } catch (HttpStatusCodeException e) {
        map.put(MESSAGE, e.getMessage());
        return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST);
    }
}

1 Ответ

0 голосов
/ 09 мая 2018

RestTemplate - это класс, а не интерфейс, и он реализует реальный HTTP-транспорт. Оба стоят на пути написания тестируемого метода. Кроме того, тот факт, что вы создаете экземпляр класса, который имеет побочные эффекты на уровне ОС, а не вводит его, не помогает в этом случае. Таким образом, способ решить это:

  • написать свой метод, основанный на интерфейсе, а не на реализации, RestOperations в данном случае
  • внедрить экземпляр, реализующий RestOperations, например экземпляр RestTemplate для производства через аргумент конструктора (предпочтительно), аргумент метода или через Supplier<RestOperations>, определенный как поле в классе
  • заменить фактический экземпляр реализацией теста или макетом в тесте. Я думаю, что для Mockito.mock(RestOperations.class) проще пойти, потому что RestOperations, как и все другие интерфейсы Spring, определяет слишком много методов для написания тестовой реализации вручную

Так что в EncoderService вы можете иметь:

private final RestOperations restClient;

public EncoderService(RestOperations restClient) {
  this.restClient = restClient;
}

public ResponseEntity<Object> createNewStream(Long channelId) {
  ...
  ResponseEntity<Object> response = restClient.postForEntity(...
  ...
}

А потом в EncoderServiceTest:

ResponseEntity<Object> expectedReturnValue = ...

RestOperations testClient = mock(RestOperations.class);
doReturn(expectedReturnValue).when(testClient).postForEntity(any(), any(), anyClass());

EncoderService service = new EncoderService(testClient);
// use the service

В остальных двух случаях настройка теста точно такая же, просто вы передаете экземпляр в вызов метода вместо конструктора или перезаписываете поставщика в экземпляре EncoderService, чтобы вернуть testClient.

Я ответил на очень похожий вопрос о ProcessBuilder, который также имеет побочные эффекты на уровне ОС и был создан непосредственно в тестируемом методе здесь Ошибка при попытке макета конструктора для ProcessBuilder с помощью PowerMockito Вы можете применять точно такую ​​же тактику.

...