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
相关产品推荐
相关产品推荐

