TDD는 아니지만, GitHub Action으로 CI/CD가 적용되어있기에 그 이점을 살리고자 테스트코드를 작성하기로 했다.
UserService의 테스트코드를 작성하며 다른 부분은 문제가 없었지만, 이메일을 보내는 것과 redis에 저장하는 것 등은 테스트코드를 어떻게 해야할까?
그것은 바로 '이메일은 보내는 것'과 'Redis에 저장되는 것'은 UserService
와 별개의 Service라고 보고 무시하는 것이다.
userService.singup()
을 했을 때, 그러한 동작을 무시하게 하려면 어떻게 해야할까!?
서칭하다보니 doNothing()
이라는 친구가 보였다.
doNothing()
: 특정 메서드가 호출되었을 때 아무런 동작을 하지 않도록 설정하는 메서드
마침 비동기 처리를 했기 때문에 이것을 적용하기 더 쉬울 것이라 판단하고 바로 적용해보았다.
위에서 얘기한 무시가 필요한 코드
...
// 인증번호 메일 보내기
String sentCode = emailAuthService.sendVerificationCode(email);
// redis에 저장하여 5분 내로 인증하도록 설정
emailAuthService.setSentCodeByLoginIdAtRedis(loginId, nickname, email,
passwordEncoder.encode(password), firstPreferredCategoryId, secondPreferredCategoryId,
sentCode);
인증번호 메일 보내는 코드는 doNothing()
으로 처리하는데 어려움이 없었다.
하지만 redis의 경우에는 조금 문제가 있었다.
여기서 아래의 코드를 mock해야되는 상황이다.
redisTemplate.opsForValue().set(loginId, sentCode, 5 * 60 * 1000, TimeUnit.MILLISECONDS);
위의 코드가 void를 return 하므로 아래와 같이 작성했다.
doNothing().when(redisTemplateMock.opsForValue())
.set(TEST_USER_LOGINID, "sent-code", anyLong(), any(TimeUnit.class));
하지만 에러메세지가 올라왔다.
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.example.jujuassembly.domain.user.service.UserServiceTest.signup(UserServiceTest.java:102)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
void인데 왜 return이 필요하다는거야... 도대체가 알 수가 없다.
한참을 헤메고 다닌 결과, 어느 블로그 글을 통해 'stack overflow'의 글을 찾을 수 있었다.
redis template를 mock하는데 어려움을 겪는다는 글이었다.
글의 답변으로는 아래와 같은 코드가 달렸다.
@Mock
RedisTemplate<String, String> redisTemplate;
@Mock
private ValueOperations valueOperations;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
Mockito.doNothing().when(valueOperations).set(anyString(), anyString());
}
ValueOperations?? 저게 도대체 무엇인가?
valueOperations: RedisTemplate에서 opsForValue()를 호출하여 얻어진 ValueOperations 객체에 대한 모의체(Mock)
"와! 그렇다면 내가 그동안 redis template의 mock을 잘못하고 있었구나!"를 깨달으며 내 코드에 저 방식을 적용해보았다.
아래와 같이 코드를 수정하니 테스트코드 정상적으로 동작하며... 꿈에 그리던 초록빛을 볼 수 있었다.
when(redisTemplateMock.opsForValue()).thenReturn(valueOperations);
doNothing().when(valueOperations).set(anyString(), anyString(), anyLong(), any(TimeUnit.class));
선배 개발자님들 존경하고 감사하고 사랑합니다💛