-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 단위 테스트 코드 추가 #133
base: main
Are you sure you want to change the base?
feat: 단위 테스트 코드 추가 #133
Changes from all commits
b2a3269
e7946be
9733f21
6dd4264
50542f7
86d1318
f358854
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package com.example.solidconnection.unit.auth.service; | ||
|
||
import com.example.solidconnection.auth.dto.ReissueResponse; | ||
import com.example.solidconnection.auth.service.AuthService; | ||
import com.example.solidconnection.config.token.TokenService; | ||
import com.example.solidconnection.config.token.TokenType; | ||
import com.example.solidconnection.custom.exception.CustomException; | ||
import com.example.solidconnection.custom.exception.ErrorCode; | ||
import com.example.solidconnection.siteuser.domain.SiteUser; | ||
import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||
import com.example.solidconnection.type.Gender; | ||
import com.example.solidconnection.type.PreparationStatus; | ||
import com.example.solidconnection.type.Role; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.data.redis.core.ValueOperations; | ||
|
||
import java.time.LocalDate; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.mockito.Mockito.*; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@DisplayName("인증 서비스 테스트") | ||
class AuthServiceTest { | ||
|
||
@InjectMocks | ||
private AuthService authService; | ||
|
||
@Mock | ||
private TokenService tokenService; | ||
|
||
@Mock | ||
private SiteUserRepository siteUserRepository; | ||
|
||
@Mock | ||
private RedisTemplate<String, String> redisTemplate; | ||
|
||
@Mock | ||
private ValueOperations<String, String> valueOperations; | ||
|
||
private static final String TEST_ACCESS_TOKEN = "testAccessToken"; | ||
private static final String TEST_REFRESH_TOKEN = "testRefreshToken"; | ||
private static final String SIGN_OUT_VALUE = "signOut"; | ||
|
||
private SiteUser testUser; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
testUser = createTestUser(); | ||
} | ||
|
||
@Test | ||
@DisplayName("로그아웃_요청시_리프레시_토큰을_무효화한다()") | ||
Comment on lines
+61
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기존 코드 컨벤션을 지키지 않고 DisplayName 어노테이션을 사용한 이유가 있나요? |
||
void shouldInvalidateRefreshTokenWhenSignOut() { | ||
// given | ||
String refreshTokenKey = TokenType.REFRESH.addTokenPrefixToSubject(testUser.getEmail()); | ||
when(redisTemplate.opsForValue()).thenReturn(valueOperations); | ||
|
||
// when | ||
authService.signOut(testUser.getEmail()); | ||
|
||
// then & verify | ||
verify(valueOperations).set( | ||
eq(refreshTokenKey), | ||
eq(SIGN_OUT_VALUE), | ||
eq(604800000L), | ||
eq(TimeUnit.MILLISECONDS) | ||
); | ||
Comment on lines
+71
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 테스트 코드는 결과가 아니라 행위를 검증하고 있는 것 같네요, 테스트 코드는 세부 구현을 담으면 위험합니다. 예를 들어 '값을 저장하는 시간'을 604800000L 에서 604800001L로 변경한다면 위 테스트 코드는 깨질 것입니다. 저라면 리프레시 토큰이 무효화되었다는 결과만 검증할 것 같습니다. 관련 글을 첨부합니다! https://ojt90902.tistory.com/1364 [테스트로 유출된 도메인 지식] 부분 |
||
} | ||
|
||
|
||
@Test | ||
@DisplayName("회원탈퇴_요청시_탈퇴일자를_설정한다()") | ||
void shouldSetQuitedAtWhenUserQuits() { | ||
// given | ||
when(siteUserRepository.getByEmail(testUser.getEmail())).thenReturn(testUser); | ||
|
||
// when | ||
authService.quit(testUser.getEmail()); | ||
|
||
// then | ||
assertThat(testUser.getQuitedAt()).isNotNull(); | ||
assertThat(testUser.getQuitedAt()).isEqualTo(LocalDate.now().plusDays(1)); | ||
Comment on lines
+91
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복 검증을 하고 있는 것 같습니다😔 테스트 코드에서 가독성은 굉장히 중요한 요소라고 생각합니다. 테스트하고자 하는 것이 무엇인지, 그것을 검증하기 위해서 반드시 필요한 것들만 있는지 다시 생각해보시면 더 좋을 것 같아요. |
||
|
||
// verify | ||
verify(siteUserRepository).getByEmail(testUser.getEmail()); | ||
Comment on lines
+94
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 대부분의 테스트 코드에 verify 를 사용하신 이유가 무엇인지 듣고 싶네요! |
||
} | ||
|
||
@Test | ||
@DisplayName("존재하지_않는_이메일로_회원탈퇴_요청시_예외를_반환한다()") | ||
void shouldThrowExceptionWhenQuitWithNonExistentEmail() { | ||
// given | ||
when(siteUserRepository.getByEmail(testUser.getEmail())) | ||
.thenThrow(new CustomException(ErrorCode.USER_NOT_FOUND)); | ||
|
||
// when & then | ||
CustomException exception = assertThrows(CustomException.class, | ||
() -> authService.quit(testUser.getEmail())); | ||
assertThat(exception.getCode()).isEqualTo(ErrorCode.USER_NOT_FOUND.getCode()); | ||
|
||
// verify | ||
verify(siteUserRepository).getByEmail(testUser.getEmail()); | ||
} | ||
|
||
@Test | ||
@DisplayName("유효한_리프레시_토큰으로_재발급_요청시_새로운_액세스_토큰을_반환한다()") | ||
void shouldReturnNewAccessTokenWhenRefreshTokenValid() { | ||
// given | ||
String refreshTokenKey = TokenType.REFRESH.addTokenPrefixToSubject(testUser.getEmail()); | ||
when(redisTemplate.opsForValue()).thenReturn(valueOperations); | ||
when(valueOperations.get(refreshTokenKey)).thenReturn(TEST_REFRESH_TOKEN); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.ACCESS)).thenReturn(TEST_ACCESS_TOKEN); | ||
|
||
// when | ||
ReissueResponse response = authService.reissue(testUser.getEmail()); | ||
|
||
// then | ||
assertThat(response.accessToken()).isEqualTo(TEST_ACCESS_TOKEN); | ||
|
||
// verify | ||
verify(valueOperations).get(refreshTokenKey); | ||
verify(tokenService).generateToken(testUser.getEmail(), TokenType.ACCESS); | ||
verify(tokenService).saveToken(TEST_ACCESS_TOKEN, TokenType.ACCESS); | ||
} | ||
|
||
@Test | ||
@DisplayName("만료된_리프레시_토큰으로_재발급_요청시_예외를_반환한다()") | ||
void shouldThrowExceptionWhenRefreshTokenExpired() { | ||
// given | ||
String refreshTokenKey = TokenType.REFRESH.addTokenPrefixToSubject(testUser.getEmail()); | ||
when(redisTemplate.opsForValue()).thenReturn(valueOperations); | ||
when(valueOperations.get(refreshTokenKey)).thenReturn(null); | ||
|
||
// when & then | ||
CustomException exception = assertThrows(CustomException.class, | ||
() -> authService.reissue(testUser.getEmail())); | ||
assertThat(exception.getCode()).isEqualTo(ErrorCode.REFRESH_TOKEN_EXPIRED.getCode()); | ||
|
||
// verify | ||
verify(valueOperations).get(refreshTokenKey); | ||
verify(tokenService, never()).generateToken(any(), any()); | ||
} | ||
|
||
private SiteUser createTestUser() { | ||
return new SiteUser( | ||
"[email protected]", | ||
"nickname", | ||
"profileImageUrl", | ||
"1999-10-21", | ||
PreparationStatus.CONSIDERING, | ||
Role.MENTEE, | ||
Gender.MALE | ||
); | ||
} | ||
} | ||
Comment on lines
+162
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일 끝 개행은 왜 해야하는걸까요? https://hyeon9mak.github.io/github-no-newline-at-a-end-of-file/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package com.example.solidconnection.unit.auth.service; | ||
|
||
import com.example.solidconnection.auth.client.KakaoOAuthClient; | ||
import com.example.solidconnection.auth.dto.SignInResponse; | ||
import com.example.solidconnection.auth.dto.kakao.FirstAccessResponse; | ||
import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest; | ||
import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse; | ||
import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto; | ||
import com.example.solidconnection.auth.service.SignInService; | ||
import com.example.solidconnection.config.token.TokenService; | ||
import com.example.solidconnection.config.token.TokenType; | ||
import com.example.solidconnection.custom.exception.CustomException; | ||
import com.example.solidconnection.custom.exception.ErrorCode; | ||
import com.example.solidconnection.siteuser.domain.SiteUser; | ||
import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||
import com.example.solidconnection.type.Gender; | ||
import com.example.solidconnection.type.PreparationStatus; | ||
import com.example.solidconnection.type.Role; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import java.time.LocalDate; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.mockito.Mockito.*; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@DisplayName("카카오 로그인 서비스 테스트") | ||
class SignInServiceTest { | ||
|
||
@InjectMocks | ||
private SignInService signInService; | ||
|
||
@Mock | ||
private SiteUserRepository siteUserRepository; | ||
|
||
@Mock | ||
private TokenService tokenService; | ||
|
||
@Mock | ||
private KakaoOAuthClient kakaoOAuthClient; | ||
|
||
private static final String TEST_ACCESS_TOKEN = "testAccessToken"; | ||
private static final String TEST_REFRESH_TOKEN = "testRefreshToken"; | ||
private static final String TEST_KAKAO_OAUTH_TOKEN = "testKakaoOauthToken"; | ||
private static final String VALID_CODE = "validCode"; | ||
private static final String INVALID_CODE = "invalidCode"; | ||
|
||
private SiteUser testUser; | ||
private KakaoUserInfoDto testKakaoUserInfo; | ||
private KakaoCodeRequest validKakaoCodeRequest; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
testUser = createTestUser(); | ||
testKakaoUserInfo = createTestKakaoUserInfoDto(); | ||
validKakaoCodeRequest = new KakaoCodeRequest(VALID_CODE); | ||
} | ||
|
||
@Test | ||
@DisplayName("기존_회원이_로그인하면_로그인_정보를_반환한다()") | ||
void shouldReturnSignInInfoWhenUserAlreadyRegistered() { | ||
// given | ||
when(kakaoOAuthClient.processOauth(VALID_CODE)).thenReturn(testKakaoUserInfo); | ||
when(siteUserRepository.existsByEmail(testUser.getEmail())).thenReturn(true); | ||
when(siteUserRepository.getByEmail(testUser.getEmail())).thenReturn(testUser); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.ACCESS)).thenReturn(TEST_ACCESS_TOKEN); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.REFRESH)).thenReturn(TEST_REFRESH_TOKEN); | ||
Comment on lines
+66
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BDD mockito 가 아니라 그냥 mockito 를 선호하시는 것 같은데, 특별한 이유가 있나요? |
||
|
||
// when | ||
KakaoOauthResponse response = signInService.signIn(validKakaoCodeRequest); | ||
|
||
// then | ||
assertThat(response).isInstanceOf(SignInResponse.class); | ||
SignInResponse signInResponse = (SignInResponse) response; | ||
assertThat(signInResponse.accessToken()).isEqualTo(TEST_ACCESS_TOKEN); | ||
assertThat(signInResponse.refreshToken()).isEqualTo(TEST_REFRESH_TOKEN); | ||
assertThat(signInResponse.isRegistered()).isTrue(); | ||
|
||
// verify | ||
verify(kakaoOAuthClient).processOauth(VALID_CODE); | ||
verify(siteUserRepository).existsByEmail(testUser.getEmail()); | ||
verify(siteUserRepository).getByEmail(testUser.getEmail()); | ||
verify(tokenService).generateToken(testUser.getEmail(), TokenType.ACCESS); | ||
verify(tokenService).generateToken(testUser.getEmail(), TokenType.REFRESH); | ||
verify(tokenService).saveToken(TEST_REFRESH_TOKEN, TokenType.REFRESH); | ||
} | ||
|
||
@Test | ||
@DisplayName("탈퇴한_회원이_로그인하면_탈퇴일자를_초기화하고_로그인_정보를_반환한다()") | ||
void shouldResetQuitedAtAndReturnSignInInfoWhenQuitedUserSignsIn() { | ||
// given | ||
testUser.setQuitedAt(LocalDate.now().minusDays(1)); | ||
|
||
when(kakaoOAuthClient.processOauth(VALID_CODE)).thenReturn(testKakaoUserInfo); | ||
when(siteUserRepository.existsByEmail(testUser.getEmail())).thenReturn(true); | ||
when(siteUserRepository.getByEmail(testUser.getEmail())).thenReturn(testUser); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.ACCESS)).thenReturn(TEST_ACCESS_TOKEN); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.REFRESH)).thenReturn(TEST_REFRESH_TOKEN); | ||
|
||
// when | ||
KakaoOauthResponse response = signInService.signIn(validKakaoCodeRequest); | ||
|
||
// then | ||
assertThat(response).isInstanceOf(SignInResponse.class); | ||
assertThat(testUser.getQuitedAt()).isNull(); | ||
|
||
// verify | ||
verify(siteUserRepository).getByEmail(testUser.getEmail()); | ||
} | ||
|
||
@Test | ||
@DisplayName("신규_회원이_로그인하면_회원가입_정보를_반환한다()") | ||
void shouldReturnSignUpInfoWhenUserNotRegistered() { | ||
// given | ||
when(kakaoOAuthClient.processOauth(VALID_CODE)).thenReturn(testKakaoUserInfo); | ||
when(siteUserRepository.existsByEmail(testUser.getEmail())).thenReturn(false); | ||
when(tokenService.generateToken(testUser.getEmail(), TokenType.KAKAO_OAUTH)).thenReturn(TEST_KAKAO_OAUTH_TOKEN); | ||
|
||
// when | ||
KakaoOauthResponse response = signInService.signIn(validKakaoCodeRequest); | ||
|
||
// then | ||
assertThat(response).isInstanceOf(FirstAccessResponse.class); | ||
FirstAccessResponse firstAccessResponse = (FirstAccessResponse) response; | ||
assertThat(firstAccessResponse.kakaoOauthToken()).isEqualTo(TEST_KAKAO_OAUTH_TOKEN); | ||
assertThat(firstAccessResponse.isRegistered()).isFalse(); | ||
assertThat(firstAccessResponse.email()).isEqualTo(testUser.getEmail()); | ||
assertThat(firstAccessResponse.nickname()).isEqualTo("testNickname"); | ||
assertThat(firstAccessResponse.profileImageUrl()).isEqualTo("testProfileImageUrl"); | ||
|
||
// verify | ||
verify(siteUserRepository).existsByEmail(testUser.getEmail()); | ||
verify(tokenService).generateToken(testUser.getEmail(), TokenType.KAKAO_OAUTH); | ||
verify(tokenService).saveToken(TEST_KAKAO_OAUTH_TOKEN, TokenType.KAKAO_OAUTH); | ||
} | ||
|
||
@Test | ||
@DisplayName("유효하지_않은_인증_코드로_로그인_시도하면_예외를_반환한다()") | ||
void shouldThrowExceptionWhenInvalidAuthCodeProvided() { | ||
// given | ||
KakaoCodeRequest invalidRequest = new KakaoCodeRequest(INVALID_CODE); | ||
when(kakaoOAuthClient.processOauth(INVALID_CODE)) | ||
.thenThrow(new CustomException(ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE)); | ||
|
||
// when & then | ||
CustomException exception = assertThrows(CustomException.class, | ||
() -> signInService.signIn(invalidRequest)); | ||
assertThat(exception.getCode()).isEqualTo(ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE.getCode()); | ||
|
||
// verify | ||
verify(kakaoOAuthClient).processOauth(INVALID_CODE); | ||
} | ||
|
||
@Test | ||
@DisplayName("카카오_리다이렉트_URI가_일치하지_않으면_예외를_반환한다()") | ||
void shouldThrowExceptionWhenKakaoRedirectUriMismatch() { | ||
// given | ||
when(kakaoOAuthClient.processOauth(VALID_CODE)) | ||
.thenThrow(new CustomException(ErrorCode.KAKAO_REDIRECT_URI_MISMATCH)); | ||
|
||
// when & then | ||
CustomException exception = assertThrows(CustomException.class, | ||
() -> signInService.signIn(validKakaoCodeRequest)); | ||
assertThat(exception.getCode()).isEqualTo(ErrorCode.KAKAO_REDIRECT_URI_MISMATCH.getCode()); | ||
|
||
// verify | ||
verify(kakaoOAuthClient).processOauth(VALID_CODE); | ||
} | ||
|
||
@Test | ||
@DisplayName("카카오_사용자_정보_조회에_실패하면_예외를_반환한다()") | ||
void shouldThrowExceptionWhenKakaoUserInfoFetchFails() { | ||
// given | ||
when(kakaoOAuthClient.processOauth(VALID_CODE)) | ||
.thenThrow(new CustomException(ErrorCode.KAKAO_USER_INFO_FAIL)); | ||
|
||
// when & then | ||
CustomException exception = assertThrows(CustomException.class, | ||
() -> signInService.signIn(validKakaoCodeRequest)); | ||
assertThat(exception.getCode()).isEqualTo(ErrorCode.KAKAO_USER_INFO_FAIL.getCode()); | ||
|
||
// verify | ||
verify(kakaoOAuthClient).processOauth(VALID_CODE); | ||
} | ||
|
||
private SiteUser createTestUser() { | ||
return new SiteUser( | ||
"[email protected]", | ||
"nickname", | ||
"profileImageUrl", | ||
"1999-10-21", | ||
PreparationStatus.CONSIDERING, | ||
Role.MENTEE, | ||
Gender.MALE | ||
); | ||
} | ||
|
||
private KakaoUserInfoDto createTestKakaoUserInfoDto() { | ||
KakaoUserInfoDto.KakaoAccountDto kakaoAccountDto = new KakaoUserInfoDto.KakaoAccountDto( | ||
new KakaoUserInfoDto.KakaoAccountDto.KakaoProfileDto("testProfileImageUrl", "testNickname"), | ||
testUser.getEmail()); | ||
return new KakaoUserInfoDto(kakaoAccountDto); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와일드카드
*
는 사용하지 않는게 좋을 것 같네요!인텔리제이에서 설정하는 방법은 링크를 참고하시면 됩니다.
https://giantdwarf.tistory.com/58