You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Spring Boot外部服务调用异常场景如何构造单元测试用例?

问题解答

疑问1:捕获HttpStatusCodeException后是否需要自定义抛出HttpStatusCodeException

是否需要调整异常抛出逻辑完全取决于上层调用方的异常处理规则:

  • 如果上层服务已经统一适配了HttpStatusCodeException的状态码解析、错误返回映射逻辑,你可以自定义抛出HttpStatusCodeException,把原异常的状态码、响应体都带上,方便上层做统一的错误转换
  • 如果上层没有针对HttpStatusCodeException的特殊处理,只是统一捕获RuntimeException打印日志,你现在的写法不需要改动,保持即可

另外注意你现有代码里有个参数不匹配的bug:String.format的格式串只有2个占位符,但是你传了4个参数,会导致日志丢失状态码、响应体这类关键错误信息,建议修改为:

String msg = String.format("Error getting data for dataId %s and author %s, status code: %s, response: %s", dataId, author, e.getStatusCode(), e.getResponseBodyAsString());

疑问2:连接失败、错误请求场景的测试用例编写

你后续尝试的测试写法逻辑有误:当接口返回非2xx状态码时,restTemplate.exchange方法会直接抛出HttpStatusCodeException,不会返回ResponseEntity对象,不需要捕获返回值判断状态码,直接用Mockito的thenThrow模拟异常抛出即可。

另外注意你现有测试代码里的路径和业务代码不匹配,业务代码请求路径是/data/{dataId}?brand={author},测试里写的是/game/{gameId}?brand={brandName},会导致mock不生效,需要修正。

场景1:模拟HTTP错误状态(4xx/5xx,验证HttpStatusCodeException捕获逻辑)

@Test
public void getData_throwRuntimeException_whenHttpErrorOccurs() {
    // 模拟返回400错误的HttpStatusCodeException
    HttpStatusCodeException mockException = new HttpClientErrorException(
        HttpStatus.BAD_REQUEST,
        "Bad Request",
        "invalid parameter".getBytes(),
        StandardCharsets.UTF_8
    );

    Mockito.when(restTemplate.exchange(
            ArgumentMatchers.eq("/data/{dataId}?brand={author}"),
            ArgumentMatchers.eq(HttpMethod.POST),
            ArgumentMatchers.any(),
            ArgumentMatchers.<Class<JsonNode>>any()
    )).thenThrow(mockException);

    // 验证方法抛出预期异常,且异常信息符合预期
    RuntimeException thrown = Assertions.assertThrows(RuntimeException.class,
        () -> httpDataService.getData("dataIdTest", Author.Potter));
    
    org.junit.jupiter.api.Assertions.assertTrue(thrown.getMessage().contains("dataIdTest"));
    org.junit.jupiter.api.Assertions.assertTrue(thrown.getMessage().contains("Potter"));
}

场景2:模拟连接失败/超时场景(验证连接层面异常处理)

连接超时、DNS解析失败这类连接层面的异常,restTemplate会抛出ResourceAccessException,你现有代码没有捕获这个异常,会直接往上抛,如果需要统一处理建议补充对应catch分支。测试代码如下:

@Test
public void getData_throwException_whenConnectFail() {
    // 模拟连接超时异常
    ResourceAccessException mockException = new ResourceAccessException("connect timeout");

    Mockito.when(restTemplate.exchange(
            ArgumentMatchers.eq("/data/{dataId}?brand={author}"),
            ArgumentMatchers.eq(HttpMethod.POST),
            ArgumentMatchers.any(),
            ArgumentMatchers.<Class<JsonNode>>any()
    )).thenThrow(mockException);

    // 如果你补充了ResourceAccessException的捕获,这里修改为验证你抛出的自定义异常即可
    Assertions.assertThrows(ResourceAccessException.class,
        () -> httpDataService.getData("dataIdTest", Author.Potter));
}

场景3:模拟接口返回空Body场景

@Test
public void getData_returnEmpty_whenResponseBodyIsNull() {
    Mockito.when(restTemplate.exchange(
            ArgumentMatchers.eq("/data/{dataId}?brand={author}"),
            ArgumentMatchers.eq(HttpMethod.POST),
            ArgumentMatchers.any(),
            ArgumentMatchers.<Class<JsonNode>>any()
    )).thenReturn(new ResponseEntity<>(null, HttpStatus.OK));

    Optional<DataResponse> result = httpDataService.getData("dataIdTest", Author.Potter);
    org.junit.jupiter.api.Assertions.assertTrue(result.isEmpty());
}

内容的提问来源于stack exchange,提问作者user9347049

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 15:51:03