Spring Boot MockMvc测试调用内部API失败问题求助
问题:Spring Boot集成测试中RestClient调用内部API失败
我正在开发一个Spring Boot应用/库,其中包含一个通过RestClient发送请求到内部API的CalculationService。集成测试时,服务的入口是/integration端点。以下是复现该错误的最小可复现代码(MVP):
主服务类
@Service @RequiredArgsConstructor public class CalculationService { private final RestClient restClient; public CalculationResponse calculate(CalculationRequest request){ return restClient.post().uri("/service/calculate") .body(request) .retrieve() .toEntity(CalculationResponse.class).getBody(); } }
主控制器
@RestController @RequestMapping("/service") public class MockApi { @PostMapping("/calculate") public CalculationResponse calculate(@RequestBody CalculationRequest calculationRequest) { CalculationResponse calculationResponse = new CalculationResponse(); calculationResponse.setSummary(calculationRequest.getName()+ "----" + calculationRequest.getValue()); return calculationResponse; } }
集成测试入口控制器
@RestController @RequestMapping @RequiredArgsConstructor public class TestController { private final CalculationService calculationService; @PostMapping("/integration") public CalculationResponse integration(@RequestBody CalculationRequest request) { return calculationService.calculate(request); } }
RestClient配置类
@Configuration public class CalculationConfiguration { @Bean public RestClient restClient() { return RestClient.builder() .baseUrl("http://localhost:8080") .build(); } }
集成测试类
@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ExtendWith(SpringExtension.class) public class IntegrationTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Test public void thatSummaryIsReturned() throws Exception { CalculationRequest request = new CalculationRequest("Test", "Value"); mockMvc.perform(MockMvcRequestBuilders.post("/integration") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andReturn(); } }
测试报错信息
Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/service/calculate": null jakarta.servlet.ServletException: Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/service/calculate": null at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
补充说明:当应用在后台运行时功能正常,似乎测试上下文缺少某些配置...
原因分析
使用@AutoConfigureMockMvc配合SpringBootTest(webEnvironment = RANDOM_PORT)时,MockMvc是在模拟Servlet环境中直接调度控制器方法,不会启动真实的HTTP服务器。但你的RestClient配置硬编码了http://localhost:8080,尝试访问真实端口的服务,而测试环境中该端口并未启动对应的服务,因此抛出I/O错误。
直接调用/service/calculate测试正常,是因为MockMvc直接路由到对应的控制器方法,无需走真实HTTP请求流程。
解决方案
方案1:动态设置RestClient的测试端口
在测试类中注入随机端口,替换RestClient的baseUrl:
@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class IntegrationTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @LocalServerPort private int port; @Autowired private CalculationService calculationService; @BeforeEach void setUp() { // 构建使用测试随机端口的RestClient RestClient restClient = RestClient.builder() .baseUrl("http://localhost:" + port) .build(); // 反射替换CalculationService中的restClient字段 Field field = ReflectionUtils.findField(CalculationService.class, "restClient"); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, calculationService, restClient); } @Test public void thatSummaryIsReturned() throws Exception { CalculationRequest request = new CalculationRequest("Test", "Value"); mockMvc.perform(MockMvcRequestBuilders.post("/integration") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andReturn(); } }
方案2:使用TestRestTemplate发送真实请求
如果不需要MockMvc的模拟能力,直接用TestRestTemplate调用测试服务器的真实端口:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class IntegrationTest { @Autowired private TestRestTemplate restTemplate; @Autowired private ObjectMapper objectMapper; @Test public void thatSummaryIsReturned() throws Exception { CalculationRequest request = new CalculationRequest("Test", "Value"); ResponseEntity<CalculationResponse> response = restTemplate.postForEntity( "/integration", request, CalculationResponse.class ); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody().getSummary()).isEqualTo("Test----Value"); } }
方案3:配置化baseUrl
将baseUrl提取到配置文件,测试时自动替换为随机端口:
- 在
application.properties中添加:
calculation.service.base-url=http://localhost:8080
- 修改RestClient配置类:
@Configuration public class CalculationConfiguration { @Value("${calculation.service.base-url}") private String baseUrl; @Bean public RestClient restClient() { return RestClient.builder() .baseUrl(baseUrl) .build(); } }
- 在
application-test.properties中添加:
calculation.service.base-url=http://localhost:${local.server.port}
内容的提问来源于stack exchange,提问作者BonBonn
相关产品推荐
相关产品推荐

