문제
사용자가 게시판에 올린 미션 인증 글을 관리자가 승인을 해주는 기능을 만들었는데,
이와 관련해서 테스트 코드를 작성하고 있었습니다.
그 중에 사용자가 올린 미션 인증 글의 상태가 승인 대기중인 글이 아닐 때, 이에 맞는 오류 메시지를 반환해야 했습니다.
@Test
@DisplayName("미션 인증글 승인 - 승인 대기중인 글이 아닐 때, 오류 메시지를 잘 반환하는지?")
void acceptMission_4() {
// given
Team team = TeamMock.create("팀1");
User user = UserMock.create(team, UserRole.MENTOR, passwordEncoder);
Mission mission = MissionMock.create();
MissionBoard missionBoard = MissionBoardMock.create(user, mission.getId(),false, RegisterStatus.REJECTED);
ReflectionTestUtils.setField(missionBoard, "id", 1L);
when(missionBoardRepository.findById(any())).thenReturn(Optional.of(missionBoard));
when(missionRepository.findById(any())).thenReturn(Optional.of(mission));
// when & then
MissionBoardNotInProgressException exception = assertThrows(MissionBoardNotInProgressException.class, () -> adminService.acceptMission(UserRole.ADMIN, missionBoard.getId()));
assertEquals(exception.getMessageId(), "failed.mission-board.in-progress");
}
위와 같이 테스트 코드를 작성하고 실행했는데, 아래와 같은 오류가 발생했습니다.

이유
이유는 생각보다 간단했습니다.
오류 메시지에서 unnecessary라고 말해주는 것처럼, 테스트 코드에서 불필요한 코드가 존재하여 이에 대한 문제를 해결해주면 되는 것이였습니다.
mockito-core 버전이 1.x일 때 없었던 테스트코드의 엄격성을 규정하기 위해 생긴 오류로,
mockio-core 2.x 버전부터 도입되었고, 3.x 버전 부터는 엄격성이 강제되었기 때문에
불필요한 stubbing 코드를 제거해야된다고 합니다.
저의 경우 해당 프로젝트에 아래와 같은 버전을 사용하고 있습니다.

이에 대한 해결 방법은 크게 3가지가 존재했습니다.
1. Mockito-core 버전을 1.x 또는 2.x로 다운그레이드 (비추천)
2.x 에서는 엄격성 개념이 도입되었지만,
테스트에 아래와 같이 MockitoSettings를 지정해주면 엄격성을 우회해서 테스트할 수 있다고합니다.
@Test
@MockitoSettings(strictness = Strictness.WARN)
void test_1() {
...
}
2. lenient() 메서드를 앞에 추가해주기
when, doReturn 등 앞에 lenient()를 추가해서 stubbing이 미사용될 수 있도록 표시해서 해결할 수 있습니다.
// lenient()를 붙여서 mocking 결과를 항상 사용하진 않음을 표시합니다.
lenient().doReturn(fixedClock.instant()).when(clock).instant();
lenient().doReturn(fixedClock.getZone()).when(clock).getZone();
3. 필요없는 stubbing 제거
사용할 필요가 없는 when, doReturn, doThrow 등을 제거해서 해결하는 방법입니다.
불필요한 테스트가 줄어들기 때문에 나중에 코드를 수정하는 사람이 테스트코드를 이해하는데 편해진다는 장점이 있습니다.
해결
저는 3번의 방법으로 해결했습니다.
/**
* 미션 인증 글 승인
*/
@Transactional
public void acceptMission(UserRole userRole, Long missionBoardId) {
if (!userRole.isAdmin()) {
throw new NotGrantedException();
}
MissionBoard missionBoard = missionBoardRepository.findById(missionBoardId).orElseThrow(PostNotFoundException::new);
if (missionBoard.getRegisterStatus().equals(ACCEPTED)) {
throw new AlreadyMissionBoardAcceptedException();
}
if (missionBoard.getRegisterStatus().equals(IN_PROGRESS)) {
Team team = missionBoard.getUser().getTeam();
Mission mission = missionRepository.findById(missionBoard.getMissionId()).orElseThrow(MissionNotFoundException::new);
if (missionBoard.isBonusMissionSuccessful()) {
BonusMission bonusMission = bonusMissionRepository.findAllByMissionId(mission.getId()).get(0);
int totalScore = mission.getPoint() + bonusMission.getPoint();
team.addScore(totalScore);
teamRepository.save(team);
} else {
team.addScore(mission.getPoint());
teamRepository.save(team);
}
missionBoard.changeRegisterStatus(ACCEPTED);
missionBoardRepository.save(missionBoard);
CompletedMission completedMission = CompletedMission.builder()
.team(team)
.mission(mission)
.isBonusSuccess(missionBoard.isBonusMissionSuccessful())
.build();
completedMissionRepository.save(completedMission);
} else {
throw new MissionBoardNotInProgressException();
}
}
위의 메소드에 대한 테스트 코드를 작성하고 있었는데,
생각을 해보니 승인 대기중인 글이 아닌 상태에 대해서 오류 메시지를 검증하는 테스트 코드 였기 때문에
if (missionBoard.getRegisterStatus().equals(IN_PROGRESS)) 이 아닌
else문으로 빠져서 throw new MissionBoardNotInProgressException(); 가 실행됩니다.
그러므로 if문 안의 missionRepository.findById(missionBoard.getMissionId())는 수행되지 않기때문에
테스트 코드에서 when(missionRepository.findById(any())).thenReturn(Optional.of(mission));는 불필요한 코드에 해당됩니다.
그러므로 테스트 코드에서 해당 when 부분을 삭제해서 해결했습니다.
@Test
@DisplayName("미션 인증글 승인 - 승인 대기중인 글이 아닐 때, 오류 메시지를 잘 반환하는지?")
void acceptMission_4() {
// given
Team team = TeamMock.create("팀1");
User user = UserMock.create(team, UserRole.MENTOR, passwordEncoder);
Mission mission = MissionMock.create();
MissionBoard missionBoard = MissionBoardMock.create(user, mission.getId(),false, RegisterStatus.REJECTED);
ReflectionTestUtils.setField(missionBoard, "id", 1L);
when(missionBoardRepository.findById(any())).thenReturn(Optional.of(missionBoard));
// when & then
MissionBoardNotInProgressException exception = assertThrows(MissionBoardNotInProgressException.class, () -> adminService.acceptMission(UserRole.ADMIN, missionBoard.getId()));
assertEquals(exception.getMessageId(), "failed.mission-board.in-progress");
}
테스트 코드가 잘 수행되는 것을 확인할 수 있었습니다.

참고
Mockito Strict Stubbing and The UnnecessaryStubbingException | Baeldung
Understand the reasons behind the Mockito UnnecessaryStubbingException and how to avoid it.
www.baeldung.com
Mockito 사용중 Unnecessary Stubbing Exception 해소하기
Mockito 사용중 Unnecessary Stubbing Exception 해소하기 Aug 15, 2021 Mockito 사용중 Unnecessary Stubbing Exception 해소하기 Spring Boot에서 JUnit5와 이에 포함된 Mockito Core 3.x 버전을 사용할 때 아래와 같이 UnnecessaryStubb
widian.github.io
Strictness in Mockito · Issue #769 · mockito/mockito
Introduction This issue explores the addition of "strict" behavior and APIs to Mockito. Traditionally, Mockito is a lenient mocking framework. For background and motivation, check out Szczepan's sh...
github.com
'SpringBoot' 카테고리의 다른 글
프로메테우스와 그라파나를 사용하여 모니터링 수행하기 (0) | 2024.03.28 |
---|---|
기존에 동시성 이슈 문제 해결을 위해 사용한 Redisson 대신 Redis의 script를 사용해서 성능 올리기 (0) | 2024.03.24 |
쿠폰 발급 요청 시, 확인하는 쿠폰 정보를 Redis Cache에 담아 개선하기 (0) | 2024.03.19 |
[Spring Boot] 채팅방에서 사용자가 업로드한 파일을 NHN Cloud의 Object Storage를 통해 관리하기 (0) | 2024.02.07 |
Spring-Data-DynamoDB를 사용하여 SpringBoot와 AWS DynamoDB 연동하기 (0) | 2024.01.28 |