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
相关产品推荐
相关产品推荐

