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

使用TestRestTemplate集成测试时已传请求体仍报缺失错误

问题

我开发了一个Spring Boot应用,该应用调用两个第三方API并根据结果返回CSV文件。应用的接口接收一个@RequestBody参数,类型为List<String>,用于第三方API调用。

尝试使用TestRestTemplate对该应用进行集成测试时,明明已向HttpEntity传入请求体,却收到HttpMessageNotReadableException: Required request body is missing错误。

将相同请求体传入Postman,或使用MockMvc进行控制器测试时均能正常运行,但我需要进行全链路的自动化端到端测试。

如果传入的内容有问题,我预期会收到JSON解析错误而非请求体缺失错误。我尝试过修改传入参数的类型,以及将Content-Type设置为text/plain,但均未改变或消除该错误。

测试代码如下:

package uk.co.heywood.sonarconverter.integration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

import uk.co.heywood.sonarconverter.Application;
import uk.co.heywood.sonarconverter.utils.TestUtils;

@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"sonar.qubeToken=aFakeQubeToken", "sonar.cloudToken=aFakeCloudToken"})
class SonarConverterIntegrationTest
{
    
    private static final String URL = "/api/sonar/diff";
    private static final String SONARQUBE_URL = "https://sonarqube.services.mywork.co.uk/api/issues/search?projects=myproject&componentKeys=myComponentKeys&statuses=OPEN&ps=500&p=1";
    private static final String SONARCLOUD_URL = "https://sonarcloud.io/api/issues/search?projects=myCloudProject&statuses=OPEN&ps=500&p=1";
    
    private static final String SONARQUBE_RESPONSE = TestUtils.fromFile( "integration/sonarqube-response.json" );
    private static final String SONARCLOUD_RESPONSE = TestUtils.fromFile( "integration/sonarcloud-response.json" );
    private static final String EXPECTED_RESULT = TestUtils.fromFile( "integration/result.csv" );
    
    @Autowired
    private TestRestTemplate testRestTemplate;
    
    @Autowired
    private RestTemplate restTemplate;
    private MockRestServiceServer mockRestServiceServer;
    
    @BeforeEach
    void setUp() {
        mockRestServiceServer = MockRestServiceServer.createServer( restTemplate );
    }
    
    @Test
    void canCreateCsv() {
        
        mockRestServiceServer.expect( ExpectedCount.once(),
                        requestTo(SONARQUBE_URL))
                .andExpect( method(HttpMethod.GET) )
                .andExpect( header( HttpHeaders.AUTHORIZATION, "Bearer aFakeQubeToken" ) )
                .andRespond( withStatus( HttpStatus.OK )
                        .contentType( MediaType.APPLICATION_JSON )
                        .body( SONARQUBE_RESPONSE ));
        mockRestServiceServer.expect( ExpectedCount.once(),
                        requestTo(SONARCLOUD_URL))
                .andExpect( method(HttpMethod.GET) )
                .andExpect( header( HttpHeaders.AUTHORIZATION, "Bearer aFakeCloudToken" ) )
                .andRespond( withStatus( HttpStatus.OK )
                        .contentType( MediaType.APPLICATION_JSON )
                        .body( SONARCLOUD_RESPONSE ));
        
        HttpHeaders headers = new HttpHeaders();
        headers.add( HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE );
        HttpEntity<String> request = new HttpEntity<>( """[
                    "aParameter", "anotherParameter"
                ]""", headers );
        ResponseEntity<String> response = testRestTemplate.exchange(
                URL,
                HttpMethod.GET,
                request,
                String.class
        );
        assertThat(response.getStatusCode()).isEqualTo( HttpStatus.OK );
        assertThat(response.getBody()).isEqualTo( EXPECTED_RESULT );
    }
}
解决方案

问题核心是GET请求不支持携带请求体:虽然部分工具(如Postman)允许GET请求带请求体,但这不符合HTTP规范,Spring的TestRestTemplate会直接忽略GET请求中的请求体,导致后端接口提示"请求体缺失"。

解决步骤:

  1. 修改控制器接口的请求方法:将接口的@GetMapping改为@PostMapping,传递List<String>这类复杂参数,POST是更合规的选择。
  2. 修改测试代码的请求方法:把测试中testRestTemplate.exchange的HttpMethod.GET替换为HttpMethod.POST。
  3. 优化请求体构造方式:避免手动拼接JSON字符串,用ObjectMapper序列化List,减少格式错误:
    // 注入ObjectMapper
    @Autowired
    private ObjectMapper objectMapper;
    
    // 构造请求体
    List<String> params = Arrays.asList("aParameter", "anotherParameter");
    String requestBody = objectMapper.writeValueAsString(params);
    HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
    

修改后的测试方法核心代码:

@Test
void canCreateCsv() {
    // ... 第三方API mock代码保持不变

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    
    List<String> params = Arrays.asList("aParameter", "anotherParameter");
    String requestBody = objectMapper.writeValueAsString(params);
    HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
    
    ResponseEntity<String> response = testRestTemplate.exchange(
            URL,
            HttpMethod.POST, // 改为POST
            request,
            String.class
    );
    
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(response.getBody()).isEqualTo(EXPECTED_RESULT);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 22:24:54