java - 在单元测试中模拟 RestTemplate#postForObject

标签 java unit-testing mockito resttemplate

给定一个类 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);
    }
}

最佳答案

RestTemplate是一个类,而不是一个接口(interface),它实现了实际的 HTTP 传输。两者都阻碍了编写可测试的方法。最重要的是,您正在构造一个对操作系统级别有副作用的类的实例,而不是将其注入(inject),这一事实对这种情况没有帮助。那么解决的方法是:

  • 基于接口(interface)而不是实现来编写方法,RestOperations在这种情况下
  • 注入(inject)一个实现 RestOperations 的实例,例如RestTemplate 的一个实例对于生产,通过构造函数参数(首选)、方法参数或通过 Supplier<RestOperations>定义为类上的字段
  • 用测试实现或测试中的模拟替换实际实例。我想选择 Mockito.mock(RestOperations.class) 更容易因为RestOperations就像所有其他 Spring 接口(interface)定义了太多手动编写测试实现的方法

所以在 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 的非常类似的问题它对操作系统级别也有副作用,并且是直接在此处测试的方法中构建的 Error trying to mock constructor for ProcessBuilder using PowerMockito您可以应用完全相同的策略。

关于java - 在单元测试中模拟 RestTemplate#postForObject,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50245444/

相关文章:

ios - Xcode 单元测试和写入数据到磁盘

java - 如何使用 mockito 和 testNg 测试枚举中的默认值

java - 可以使用 Whitebox 类中的静态实用方法吗?

java - 在 Spring Security 中使用自定义过滤器时,Spring 单元测试 MockMvc 失败

javascript - 错误 : getItem() method does not exist (Angular, 模拟 LocalStorage)

java - PowerMock 抛出 java.lang.ExceptionInInitializerError

java - 将模板应用到现有 HTML 代码的简单方法

java - SpringBoot @WebMvcTest 安全问题

java - 为什么此代码中需要 "finally" block

java - 正则表达式匹配正斜杠之后或中间的单词