会员注册Controller单元测试状态码异常:预期201实际返回200
问题排查:单元测试返回200而非预期201状态码
问题根源
你的MemberApiController的join方法返回自定义BaseResponse对象时,Spring MVC默认会将这类普通对象返回视为成功响应,默认返回200 OK状态码。而Postman调用能返回201,大概率是你的项目中通过全局响应拦截器、统一返回值处理逻辑等方式,在实际运行时额外绑定了HTTP状态码,但单元测试环境没有触发该逻辑,或者控制器方法本身没有显式指定HTTP响应状态码。
解决方案
两种常见修复方式:
1. 改用ResponseEntity包装返回值
修改控制器方法,显式通过ResponseEntity指定HTTP状态码:
@PostMapping(value = "/join", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<BaseResponse<MemberResponseDto>> join( @RequestPart(value = "profileImage", required = false) final MultipartFile image, @Valid @RequestPart("requestDto") final MemberSaveRequestDto requestDto ) throws IOException { final MemberResponseDto result = memberService.saveMember(image, requestDto); return ResponseEntity.status(BaseResponseStatus.JOIN_SUCCESS.getHttpStatus()) .body(BaseResponse.of(BaseResponseStatus.JOIN_SUCCESS, result)); }
确保BaseResponseStatus.JOIN_SUCCESS中持有HttpStatus.CREATED实例。
2. 在控制器方法上添加@ResponseStatus注解
如果不想修改返回类型,直接在方法上标注对应状态码即可:
@PostMapping(value = "/join", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.CREATED) public BaseResponse<MemberResponseDto> join( @RequestPart(value = "profileImage", required = false) final MultipartFile image, @Valid @RequestPart("requestDto") final MemberSaveRequestDto requestDto ) throws IOException { final MemberResponseDto result = memberService.saveMember(image, requestDto); return BaseResponse.of(BaseResponseStatus.JOIN_SUCCESS, result); }
验证修改
调整后重新运行单元测试,result.andExpect(status().isCreated())会正常通过,因为Spring会正确将HTTP响应状态码设置为201。
相关代码参考
1. MemberApiController 原代码
@PostMapping(value = "/join", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) public BaseResponse<MemberResponseDto> join( @RequestPart(value = "profileImage", required = false) final MultipartFile image, @Valid @RequestPart("requestDto") final MemberSaveRequestDto requestDto ) throws IOException { final MemberResponseDto result = memberService.saveMember(image, requestDto); return BaseResponse.of( BaseResponseStatus.JOIN_SUCCESS, // HttpStatus.CREATED result ); }
2. MemberService 代码
@Transactional public MemberResponseDto saveMember(final MultipartFile image, final MemberSaveRequestDto requestDto) throws IOException { validateSingUpRequest(requestDto); checkPasswordEquals(requestDto.password(), requestDto.checkPassword()); final String profileImage = saveProfileImage(image); final Member member = Member.builder() .nickname(requestDto.nickname()) .profileImage(profileImage) .email(requestDto.email()) .password(passwordEncoder.encode(requestDto.password())) .phoneNumber(requestDto.phoneNumber()) .role(MemberRole.ROLE_GENERAL) .status(MemberStatus.ACTIVE) .build(); memberRepository.save(member); return new MemberResponseDto(member); }
3. MemberApiControllerTest 代码
@ParameterizedTest @AutoSource @Customization(MemberSaveCustomization.class) void join_success(final MemberSaveRequestDto requestDto) throws Exception { // given -- 测试状态设置 final MockMultipartFile image = new MockMultipartFile( "profileImage", "profile.jpg", IMAGE_JPEG_VALUE, "test profile content".getBytes(UTF_8) ); final Member member = Member.builder() .nickname(requestDto.nickname()) .profileImage(image.getName()) .email(requestDto.email()) .password(passwordEncoder.encode(requestDto.password())) .phoneNumber(requestDto.phoneNumber()) .role(ROLE_GENERAL) .status(ACTIVE) .build(); final var request = objectMapper.writeValueAsString(requestDto); final var response = new MemberResponseDto(member); given(memberService.saveMember(any(), any())).willReturn(response); // when -- 执行测试行为 final var result = mockMvc.perform( multipart("/api/members/join") .file(image) .file(new MockMultipartFile("requestDto", "", APPLICATION_JSON_VALUE, request.getBytes(UTF_8))) .accept(APPLICATION_JSON_VALUE) .contentType(MULTIPART_FORM_DATA_VALUE) ); // then -- 验证预期结果 result.andExpect(status().isCreated()); }
4. 测试执行结果
MockHttpServletRequest: HTTP Method = POST Request URI = /api/members/join Parameters = {} Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Accept:"application/json"] Body = null Session Attrs = {} Handler: Type = custom.capstone.domain.members.api.MemberApiController Method = custom.capstone.domain.members.api.MemberApiController#join(MultipartFile, MemberSaveRequestDto) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = application/json Body = {"code":1000,"message":"会员注册已完成","result":{"nickname":"test","profileImage":"profileImage","email":"test@test.com","phoneNumber":"010-1234-5678","password":null}} Forwarded URL = null Redirected URL = null Cookies = [] Status Expected :201 Actual :200
内容的提问来源于stack exchange,提问作者이승희
相关产品推荐
相关产品推荐

