오류 내용을 간단히 요약하면, 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]
내 오류와 관련 있는 해결 방법은 아니지만, 위의 참고 링크를 통해 해결했다.
RoomControllerTest에서 사용하는 RoomDto 클래스에, hashCode와 equals 메서드를 Override 하니, 테스트가 정상적으로 통과하였다.
230907 내용 추가
위의 코드에서, 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 |