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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 01:40:26