오류 내용을 간단히 요약하면, Controller와 Service 코드는 정상적으로 작성되었으며,
WebMvcTest에서 내에서 Mockito.when().thenReturn() 을 통해 정상적으로 Mocking을 했음에도 불구하고 테스트 코드의 결과가 내가 예상한 대로 나오지 않는 상황이다.
오류 상황
RoomController
@PostMapping(value = "/room", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ApiResponse<?>> createRoom(@Valid @RequestBody RoomDto roomDto) {
int result = roomService.createRoom(roomDto);
String roomId = roomDto.getRoomId();
if (result == HttpStatus.CREATED.value()) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(success(ROOM_CREATED, roomId));
} else if (result == 0) {
// 테스트를 위해 임의로 값이 0이면 오류 출력하도록 설정
return ResponseEntity.status(result)
.body(ApiResponse.error(ErrorCode.SERVER_ERROR));
}
return null;
}
RoomService
public int createRoom(RoomDto roomDto) {
// Dto -> Entity
Room room = roomDto.toRoom();
// save db
try {
room.setRoomId(roomDto.getRoomId()); // Assign a value to the ID field manually
roomRepository.save(room);
} catch (DataIntegrityViolationException de) {
// room Entity의 room_id 가 중복되는 경우
// UUID를 사용하기 때문에 중복될 가능성이 매우 낮지만, 혹시 모르니 예외처리
log.error("DataIntegrityViolationException err : ", de);
return HttpStatus.INTERNAL_SERVER_ERROR.value();
} catch (Exception e) {
log.error("Exception err : ", e);
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
return HttpStatus.CREATED.value();
}
RoomControllerTest
@Test
@DisplayName("채팅방 생성이 정상적으로 진행되었을 때, 201 응답과 그에 해당하는 메시지가 DTO 객체로 리턴된다.")
void createRoom() throws Exception {
RoomDto roomDto = new RoomDto("testroom1", "testmentee1", "testmentor1");
String content = gson.toJson(roomDto);
Mockito.when(roomService.createRoom(roomDto)).thenReturn(201);
mvc.perform(MockMvcRequestBuilders.post("/api/chat/room")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code", is(201)))
.andExpect(jsonPath("$.message", is("채팅방이 정상적으로 생성되었습니다")))
.andExpect(jsonPath("$.data", is("testroom1")))
.andDo(print());
verify(roomService, times(1)).createRoom(roomDto);
}
RoomControllerTest 의 createRoom 메서드 테스트 결과
- java.lang.AssertionError: Status expected:<201> but was:<0> 오류 발생
- Mockito.when().thenReturn() 으로 값을 주었음에도 불구하고, RoomController에서 int result의 값이 201이 아닌 0이 받아지는 오류였다.
해결 방법
MockMvc test POST request returns error : Actual invocations have different arguments: [duplicate]
MockMvc test POST request returns error : Actual invocations have different arguments:
I am new to backend testing and I am testing my spring boot app with MockMvc. However, when I want to test my Post request, I have the error mentioned in the title when I use verify(userService,
stackoverflow.com
내 오류와 관련 있는 해결 방법은 아니지만, 위의 참고 링크를 통해 해결했다.
RoomControllerTest에서 사용하는 RoomDto 클래스에, hashCode와 equals 메서드를 Override 하니, 테스트가 정상적으로 통과하였다.
230907 내용 추가
Spring boot @MockMvcTest MockHttpServletResponse always returns empty body
I'm struggling with a simple spring boot rest controller test which always return empty body response. Here is my test code looks like: @WebMvcTest(AdminRestController.class) @AutoConfigureMockMvc(
stackoverflow.com
위의 코드에서, andExpect() 부분을 모두 제거하고, verify() 코드를 실행해보면 오류 원인을 자세하게 확인할 수 있다.
@Test
@DisplayName("채팅방 생성이 정상적으로 진행되었을 때, 201 응답과 그에 해당하는 메시지가 DTO 객체로 리턴된다.")
void createRoom() throws Exception {
RoomDto roomDto = new RoomDto("testroom1", "testmentee1", "testmentor1");
String content = gson.toJson(roomDto);
Mockito.when(roomService.createRoom(roomDto)).thenReturn(201);
mvc.perform(MockMvcRequestBuilders.post("/api/chat/room")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andDo(print());
verify(roomService, times(1)).createRoom(roomDto);
}
실행 결과 아래와 같은 오류 메시지를 확인할 수 있다.
Argument(s) are different! Wanted:
seoultech.capstone.menjil.domain.follow.application.FollowService#0 bean.followRequest(
seoultech.capstone.menjil.domain.follow.dto.request.FollowRequest@1dbc607d
);
-> at seoultech.capstone.menjil.domain.follow.api.FollowControllerTest.followRequest_follow_deleted(FollowControllerTest.java:103)
Actual invocations have different arguments:
seoultech.capstone.menjil.domain.follow.application.FollowService#0 bean.followRequest(
seoultech.capstone.menjil.domain.follow.dto.request.FollowRequest@1dd64243
);
-> at seoultech.capstone.menjil.domain.follow.api.FollowController.followRequest(FollowController.java:33)
오류 메시지를 확인하면, Mockito.when().thenReturn() 에서 사용한 객체와(RoomDto), verify에서 사용하는 RoomDto의 객체가 일치하지 않아서 발생하는 원인인 것을 파악할 수 있다.
아래 사진은 위의 stackoverflow의 답변인데, 이를 참고하면.
나의 경우 Gson 라이브러리를 사용하여 RoomDto를 String 객체로 변환한 뒤, Mockmvc 를 사용하여 post 요청을 보냈는데, 변환한 다음 equals() 및 hashCode() 가 오버라이드 되어있지 않아서, 변환한 content 객체를 RoomDto와 다른 객체로 Mockito가 판단해서 오류가 발생하였다고 생각한다.
혹은 다음과 같이 사용이 가능하다.
Mockito.any(...) 사용해서 어떠한 객체가 전달되던지에 관계없이 모의 동작을 수행하도록 한다.
@Test
@DisplayName("로그인 시 google, kakao 외에 다른 플랫폼이 온 경우 ErrorCode.PROVIDER_NOT_ALLOWED 리턴")
void signIn_provider_type_mismatch() throws Exception {
// given
String providerMisMatch = "naver";
SignInRequest request = new SignInRequest("k337kk@kakao.com", providerMisMatch);
String content = gson.toJson(request);
// when
Mockito.when(authService.signIn(Mockito.any(SignInServiceRequest.class)))
.thenThrow(new CustomException(ErrorCode.PROVIDER_NOT_ALLOWED));
// then
mockMvc.perform(MockMvcRequestBuilders.post("/api/auth/signin")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code",
is(ErrorCode.PROVIDER_NOT_ALLOWED.getHttpStatus().value())))
.andExpect(jsonPath("$.message",
is(ErrorCode.PROVIDER_NOT_ALLOWED.getMessage())))
.andDo(print());
verify(authService, times(1)).signIn(Mockito.any(SignInServiceRequest.class));
}
아래와 같이 사용하면, 객체의 동일성을 보장하지 못할 수 있다.
request.toServiceRequest()를 호출하면 새로운 SignInServiceRequest 인스턴스가 생성된다. 테스트에서 이 메소드를 호출할 때마다 새로운 객체가 생성되므로, 실제 서비스 메소드 호출 시 사용되는 인스턴스와 Mockito 설정에 사용된 인스턴스가 서로 다를 수 있다.
마찬가지로 해결책은, SignInServiceRequest에 equals(), hashcode() 메서드를 재정의하면 통과한다. 하지만 위처럼 Mockito.any()를 사용하는 것이 더 적절해보인다고 생각함.
@Test
@DisplayName("로그인 시 google, kakao 외에 다른 플랫폼이 온 경우 ErrorCode.PROVIDER_NOT_ALLOWED 리턴")
void signIn_provider_type_mismatch() throws Exception {
// given
String providerMisMatch = "naver";
SignInRequest request = new SignInRequest("k337kk@kakao.com", providerMisMatch);
String content = gson.toJson(request);
// when
Mockito.when(authService.signIn(request.toServiceRequest()))
.thenThrow(new CustomException(ErrorCode.PROVIDER_NOT_ALLOWED));
// then
mockMvc.perform(MockMvcRequestBuilders.post("/api/auth/signin")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code",
is(ErrorCode.PROVIDER_NOT_ALLOWED.getHttpStatus().value())))
.andExpect(jsonPath("$.message",
is(ErrorCode.PROVIDER_NOT_ALLOWED.getMessage())))
.andDo(print());
// 이 경우 오류 발생!
verify(authService, times(1)).signIn(request.toServiceRequest());
}
'Spring Boot > Test Code' 카테고리의 다른 글
Junit5] JwtAuthenticationFilter 테스트 코드 작성 (0) | 2023.07.24 |
---|---|
Junit5] MongoDB 테스트 환경 세팅 (0) | 2023.07.17 |
Junit5] WebMvcTest: java.lang.AssertionError: JSON path (0) | 2023.07.15 |
Junit5] 테스트 코드 작성을 위한 서비스 코드 리팩토링 (0) | 2023.07.14 |
Junit5] org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false (0) | 2023.07.08 |