使用WireMock模拟API时遭遇Invalid response:3错误求助
使用WireMock模拟API测试Spring Boot接口时遇到"I/O error on GET request: Invalid response: 3"异常
问题描述
在使用WireMock模拟第三方API响应、测试Spring Boot接口时,始终抛出异常:I/O error on GET request for "http://localhost:56994": Invalid response: 3。业务代码正常运行,但测试时无法获取WireMock的模拟响应,直接进入ResourceAccessException捕获分支,返回500错误。
已完成的配置与尝试
1. WireMock测试配置
WireMock通过动态端口启动,日志显示启停正常,配置代码如下:
@SpringBootTest @AutoConfigureMockMvc public class WeatherControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @RegisterExtension public static WireMockExtension wireMockServer = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); @DynamicPropertySource public static void propertyOverride(DynamicPropertyRegistry registry) { registry.add("api.weather.cityNames", () -> "Jastarnia"); registry.add("api.weather.apiKey", () -> "123"); registry.add("api.weather.baseUrl", wireMockServer::baseUrl); } }
2. WireMock模拟响应的两种尝试
方式一:通过实体类生成JSON响应
//given FetchWeatherResponse fetchWeatherResponse = new FetchWeatherResponse( "Jastarnia", Arrays.asList( new FetchWeatherResponse.WeatherData(20.5, 10.2, "2024-01-05"), new FetchWeatherResponse.WeatherData(18.9, 8.6, "2024-01-06") ) ); String json = objectMapper.writeValueAsString(fetchWeatherResponse); wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/")) .withQueryParam("city", WireMock.equalTo("Jastarnia")) .withQueryParam("key", WireMock.equalTo("123")) .willReturn(aResponse() .withStatus(OK) .withHeader("Content-Type", APPLICATION_JSON) .withBody(json)));
方式二:直接使用JSON字符串作为响应体
.willReturn(aResponse() .withStatus(OK) .withHeader("Content-Type", APPLICATION_JSON) .withBody("""{ "city_name": "Warsaw", "data": [ { "temp": 20.5, "wind_spd": 10.2, "datetime": "2024-01-05" }, { "temp": 18.9, "wind_spd": 8.6, "datetime": "2024-01-06" } ] }""".trim())));
两种方式均触发相同异常。
业务代码实现
@Override public List<FetchWeatherResponse> fetchWeather(List<String> urls) { final HttpEntity<HttpHeaders> requestEntity = new HttpEntity<>(createHeader()); List<FetchWeatherResponse> fetchedWeathers = new LinkedList<>(); try { for (String uri : urls) { FetchWeatherResponse fetchedWeather = makeWeatherRequest(requestEntity, uri); if(fetchedWeather == null) { log.error("Response body was null."); throw new ResponseStatusException(NO_CONTENT); } fetchedWeathers.add(fetchedWeather); } return fetchedWeathers; } catch (ResourceAccessException e) { log.error("Error while fetching locations: " + e.getMessage()); throw new ResponseStatusException(INTERNAL_SERVER_ERROR); } } private FetchWeatherResponse makeWeatherRequest(HttpEntity<HttpHeaders> requestEntity, String url) { ResponseEntity<FetchWeatherResponse> response = restTemplate.exchange( url, GET, requestEntity, new ParameterizedTypeReference<>() { }); log.info("data downloaded successfully"); return response.getBody(); }
响应实体类定义
@Data @AllArgsConstructor @NoArgsConstructor public class FetchWeatherResponse { @JsonProperty("city_name") String cityName; @JsonProperty("data") List<WeatherData> data; @Data @AllArgsConstructor @NoArgsConstructor public static class WeatherData { @JsonProperty("temp") double temperature; @JsonProperty("wind_spd") private double windSpd; @JsonProperty("datetime") private String datetime; } }
测试代码
MvcResult response = mockMvc.perform(get("/api/best-weather?date=2024-01-05") .contentType(APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andReturn();
完整错误日志
p.b.w.domain.weather.WeatherServiceImpl : generated url http://localhost:57135?city=Jastarnia&key=123 2024-01-06T00:28:25.285+01:00 ERROR 16300 --- [ main] p.b.w.i.fetch.FetchWeatherImpl : Error while fetching locations: I/O error on GET request for "http://localhost:57135": Invalid response: 3 MockHttpServletRequest: HTTP Method = GET Request URI = /api/best-weather Parameters = {date=[2024-01-05]} Headers = [Content-Type:"application/json;charset=UTF-8"] Body = null Session Attrs = {} Handler: Type = pl.bartoszmech.weather.application.controller.WeatherController Method = pl.bartoszmech.weather.application.controller.WeatherController#getWeather(String) Async: Async started = false Async result = null Resolved Exception: Type = org.springframework.web.server.ResponseStatusException ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 503 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"message":"500 INTERNAL_SERVER_ERROR"} Forwarded URL = null Redirected URL = null Cookies = [] java.lang.AssertionError: Status expected:<200> but was:<503> Expected :200 Actual :503
已尝试的排查动作
- 使用不同格式的响应体(实体类序列化、手动JSON字符串)
- 手动拼接URL,不使用查询参数
问题依旧,求解决思路。
内容的提问来源于stack exchange,提问作者FuuKowatty
相关产品推荐
相关产品推荐

