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

Spring Boot测试:POST请求状态码201但响应体为空排查

问题:Spring Boot POST端点返回201但响应体为空

我在测试Spring Boot应用中创建ChallengeReport的POST端点时,虽然返回状态码正确为201 Created,但响应体始终是空的。需要找出响应体未正常返回的原因,以及测试或控制器配置中缺失的内容,确保响应体能正常返回。

控制器代码

@RestController
@RequestMapping("/call")
public class UpdateController {

  private ReportsService reportsService;

  @Autowired
  public UpdateController(ReportsService reportsService) {
    this.reportsService = reportsService;
  }

  @PostMapping("/createChallengeReport")
  @ResponseStatus(HttpStatus.CREATED)
  public ResponseEntity<ChallengeReport> createChallengeReport(@RequestBody ReportDTO dto) {
    ChallengeReport challengeReport = reportsService.createChallengeReport(dto);
    return ResponseEntity.status(HttpStatus.CREATED).body(challengeReport);
  }
//...

服务层代码

public ChallengeReport createChallengeReport(ReportDTO dto) {

    // 将DTO转换为ChallengeReport实体
    ModelMapper modelMapper = new ModelMapper();
    ChallengeReport challengeReport = modelMapper.map(dto, ChallengeReport.class);

    // 检查用户是否存在
    Optional<User> optionalUser = userRepository.findById(challengeReport.getUser().getId());
    if (optionalUser.isEmpty()) {
      throw new NotFoundException("用户不存在");
    }
    var user = optionalUser.get();

    Optional<Challenge> optionalChallenge = challengeRepository.findById(challengeReport.getChallenge().getId());
    if (optionalChallenge.isEmpty()) {
      throw new NotFoundException("挑战不存在");
    }
    var challenge = optionalChallenge.get();

    // 检查该用户针对此挑战的报告是否已存在
    boolean reportExists = challengeReportRepository.existsByChallengeIdAndUserId(
        challengeReport.getChallenge().getId(), dto.getUserId());
    if (reportExists) {
      throw new AlreadyExistsException("该挑战报告已存在");
    }

    var challengeSummary = challengeSummaryRepository.findByUser(user).get();
    challengeSummary.setChallengeCount(challengeSummary.getChallengeCount() + 1);
    challengeSummary.setPendingCount(challengeSummary.getPendingCount() + 1);

    // 保存并返回新的ChallengeReport
    challengeReportRepository.save(challengeReport);

    return challengeReport;
  }

测试代码

java.util.Date date = new Date();

  @BeforeEach
  public void setUp() {
    user = new User();
  }

  // 测试createChallengeReport返回201状态码
  @Test
  public void createChallengeReportTest() throws Exception {
    challenge = new Challenge("挑战1", "描述1", Unit.KM, 2.0, date,
        1, 2, user, Visibility.PUBLIC);
    challengeReport = new ChallengeReport(challenge, user, "挑战1", date,
        "用户1", "描述1");
    var dto = new ReportDTO(1L, 1L, "挑战1", date, date, "用户1",
        "描述1", ChallengeStatus.OPEN);

    given(this.reportsService.createChallengeReport(dto)).willReturn(challengeReport);

    ObjectWriter ow = objectMapper.writer().withDefaultPrettyPrinter();
    String requestJson = ow.writeValueAsString(dto);

    this.mvc.perform(post("/call/createChallengeReport")
        .accept(MediaType.APPLICATION_JSON)
        .contentType(MediaType.APPLICATION_JSON)
        .content(requestJson))
        .andDo(print())
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.name").value("挑战1"))
        .andExpect(jsonPath("$.description").value("描述1"))
        .andExpect(jsonPath("$.createdBy").value("用户1"));
  }

调试控制台输出

MockHttpServletResponse:
           Status = 201
    Error message = null
          Headers = []
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

MockHttpServletResponse:
           Status = 201
    Error message = null
          Headers = []
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

ChallengeReport实体类

@Entity
public class ChallengeReport {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  @JoinColumn(name = "challenge_FK")
  private Challenge challenge;

  // 用户外键
  @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  @JoinColumn(name = "user_FK")
  private User user;

  private String name;
  private Date startDate;
  private Date endDate;
  private String createdBy;
  private String description;

  @Enumerated(EnumType.STRING)
  private ChallengeStatus status;

  public ChallengeReport() {
    this.status = ChallengeStatus.OPEN;
  }

  @Autowired
  public ChallengeReport(Challenge challenge, User user, String name, Date startDate, String createdBy,
      String description) {

    this.user = user;
    this.challenge = challenge;
    this.name = name;
    this.startDate = startDate;
    this.endDate = null;
    this.createdBy = createdBy;
    this.description = description;
    this.status = ChallengeStatus.OPEN;
  }

问题原因分析及解决方案

1. 控制器注解冲突

控制器方法同时使用了@ResponseStatus(HttpStatus.CREATED)和ResponseEntity返回逻辑,两者会产生冲突:@ResponseStatus会覆盖ResponseEntity的设置,只返回状态码而忽略响应体。

解决方法:
移除方法上的@ResponseStatus(HttpStatus.CREATED)注解,仅保留ResponseEntity的返回逻辑:

@PostMapping("/createChallengeReport")
public ResponseEntity<ChallengeReport> createChallengeReport(@RequestBody ReportDTO dto) {
  ChallengeReport challengeReport = reportsService.createChallengeReport(dto);
  return ResponseEntity.status(HttpStatus.CREATED).body(challengeReport);
}

2. 实体类缺少序列化支持

Spring Boot默认用Jackson做JSON序列化,若实体类没有为私有字段提供getter方法,Jackson无法读取属性值,导致响应体为空。你的ChallengeReport实体仅定义了构造方法,没有getter。

解决方法:

  • 手动为所有私有字段添加getter方法;
  • 或使用Lombok的@Data注解自动生成getter、setter等方法:
@Entity
@Data
public class ChallengeReport {
  // ... 现有代码 ...
}

3. 关联对象序列化问题

ChallengeReport关联了User和Challenge实体,若这些关联对象没有getter方法,或不需要在响应中返回,会导致序列化失败或冗余数据。

解决方法:

  • 若不需要返回关联对象,在对应字段上添加@JsonIgnore注解:
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "challenge_FK")
@JsonIgnore
private Challenge challenge;
  • 若需要返回关联对象,确保User和Challenge实体也有getter方法。

4. 测试对象初始化问题

测试中mock返回的challengeReport需确保所有属性都已正确初始化,同时检查测试类的ObjectMapper是否能处理Date类型和枚举类型的序列化。


内容的提问来源于stack exchange,提问作者Adrian Krafft

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 06:22:05