배경
@RestController
@RequiredArgsConstructor
public class ScheduleController {
private final ScheduleService scheduleService;
@GetMapping("schedules")
public ResponseEntity<CommonResponse> getSchedules(
ScheduleQueryParamDto scheduleQueryParamDto) {
ArrayList<ScheduleTeam> schedules = scheduleService.getSchedules(scheduleQueryParamDto);
return ResponseEntity.ok(CommonResponse.ok(schedules));
}
}
다음과 같은 controller 가 있을때 controller layer unit test를 작성하고자 했다.
@Autowired
private MockMvc mockMvc;
@MockBean
ScheduleService scheduleService;
@Test
@DisplayName("[SUCCESS] 일정 목록 조회 테스트")
public void getSchedulesTest() throws Exception {
//given
ArrayList<ScheduleTeam> scheduleTeams = new ArrayList<>();
scheduleTeams.add(new ScheduleTeam(1, "LG vs SSG", "잠실종합운동장", "2024-04-01 18:30:00"));
ScheduleQueryParamDto queryParam = new ScheduleQueryParamDto(1, 1, 1, 1);
given(scheduleService.getSchedules(queryParam)).willReturn(scheduleTeams);
//when, then
mockMvc.perform(get("/schedules")
.param("teamId", String.valueOf(queryParam.getTeamId()))
.param("month", String.valueOf(queryParam.getMonth()))
.param("day", String.valueOf(queryParam.getDay()))
.param("stadiumId", String.valueOf(queryParam.getStadiumId()))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.data[0].scheduleId").value(scheduleTeams.get(0)
.getScheduleId()));
}
따라서 이렇게 테스트 코드를 작성했다. controller가 service에 의존하고 있으므로 ScheduleService 를 Mock 객체로 만들었다.
ArrayList<ScheduleTeam> schedules = scheduleService.getSchedules(scheduleQueryParamDto);
그리고 scheduleService.getSchedules(scheduleQueryParamDto) 의 dto를 given으로 아무값이나 주려고 했다. 아무값이나 주어도 Mock 객체니까 return 해주는 값만 잘 넣어주면 된다고 생각했다. 하지만 실제 dto 를 넣었더니 body값이 null로 나와서 jsonPath("$.data[0].scheduleId")에서 에러가 떴다.
원인
왜일까?
ScheduleQueryParamDto scheduleQueryParamDto = new ScheduleQueryParamDto(1, 1, 1, 1);
given(scheduleService.getSchedules(scheduleQueryParamDto)).willReturn(scheduleTeams);
테스트 코드에서 dto의 객체를 전달하면, 테스트 코드 상에서 만든 dto 객체의 주소와 controller layer의 dto객체의 주소가 서로 다르므로 에러가 뜨는 것이었다.
그러다 문득 궁금해졌다. 그럼 객체의 주소가 아닌 객체 내용을 비교하도록 하면 되는거 아닌가?🤔
given() 에서 equals()를 써도 되는지 찾아봤다.
Mockito - mockito-core 5.11.0 javadoc
Mockito - mockito-core 5.11.0 javadoc
Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 5.11.0 https://javadoc.io/doc/org.mockito/mockito-core/5.11.0 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org
javadoc.io
"Mockito verifies argument values in natural java style: by using an equals() method."
이라고 정확히 써져있었고 dto에 equals()를 구현해서 확인해보았다.
@Getter
@EqualsAndHashCode //추가
public class ScheduleQueryParamDto {
private final Integer teamId;
private final Integer month;
private final Integer day;
private final Integer stadiumId;
public ScheduleQueryParamDto(Integer teamId, Integer month, Integer day, Integer stadiumId) {
this.teamId = teamId;
// month 필드가 null이라면 현재 월 설정
this.month = month == null ? LocalDateTime.now()
.getMonthValue() : month;
this.day = day;
this.stadiumId = stadiumId;
}
}
정상적으로 테스트 통과했다.
결과 및 알게된 점
하지만! 위의 코드는 어떤 파라미터값이 들어오는지 검증하는 것이 아니라 HTTP 요청을 통한 데이터 반환이 정확한지 확인하는 테스트 코드이므로 any()를 사용해 모든 값을 일치시키는게 더 편리한 방법이다.
ArgumentMatchers - mockito-core 5.11.0 javadoc
ArgumentMatchers - mockito-core 5.11.0 javadoc
Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 5.11.0 https://javadoc.io/doc/org.mockito/mockito-core/5.11.0 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org
javadoc.io
public class ArgumentMatchers {
public static <T> T any() {
reportMatcher(Any.ANY);
return null;
}
any()는 Mockito의 ArgumentMatchers의 메소드이며, Mock객체에서 메소드 호출시 전달되는 매개변수의 일치여부를 확인하지 않고 null를 포함한 모든 값을 일치시킨다. 즉 파라미터에 어떤 값이 들어오든 모든 값을 일치시킨다는 것을 알 수 있다.
따라서 이렇게 any()를 사용하도록 테스트 코드를 수정하였다.
@Test
@DisplayName("[SUCCESS] 일정 목록 조회 테스트")
public void getSchedulesTest() throws Exception {
//given
ArrayList<ScheduleTeam> scheduleTeams = new ArrayList<>();
scheduleTeams.add(new ScheduleTeam(1, "LG vs SSG", "잠실종합운동장", "2024-04-01 18:30:00"));
given(scheduleService.getSchedules(any())).willReturn(scheduleTeams);
//when, then
mockMvc.perform(get("/schedules"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data[0].scheduleId").value(scheduleTeams.get(0)
.getScheduleId()));
}
이 과정에서 테스트 코드에서 무엇을 정확히 테스트해야할지 파악해야한다는 것을 깨달았다. 테스트 코드를 좀 더 다양하게 만들어봐야겠다.😃
'Spring' 카테고리의 다른 글
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (3) - Connection Pool 크기 조절 (0) | 2024.05.19 |
---|---|
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (2) - 병목지점 파악 (0) | 2024.05.14 |
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (1) - 환경 구축 (0) | 2024.05.10 |
Redis Lua Script 실제로 Atomic할까? (0) | 2024.04.18 |
Redis Lua Script 와 synchronized 간의 성능 비교 테스트 (+ 수정) (0) | 2024.04.15 |