서론
public TokenDto signUp(AppleSignUpRequestDto signUpRequest) {
AppleUserInfo appleUserInfo = appleIDTokenValidator.extractAppleUserinfoFromIDToken(signUpRequest.getIdentityToken());
if(signUpRequest.getName() == null || signUpRequest.getName().isEmpty()) {
throw new ApiException(AppleTokenStatus.INVALID_SIGNUP_FORM);
}
appleUserInfo.setName(signUpRequest.getName());
MemberEntity member = MemberConverter.toEntity(appleUserInfo);
memberService.signInOrUp(member);
TokenDto token = tokenHelper.createToken(member.getEmail());
return token;
}
이 코드를 보다가 이 코드는 하나의 동작을 하는 것 같지 않다는 생각이 들었다.
- AppleSignUpRequestDto를 AppleUserInfo로 변경을 한다.
- 이름이 없으면 에러를 뱉어낸다.
- memberService에서 signInOrUp을 한다.
- tokenHelper를 통해서 Token을 생성한다.
무려 4가지 일을 하고 있는 것이다. 이는 Single Response Principle에 위배되는 것 같다.
리팩터링
그래서 메서드가 하나의 요구사항은 반드시 들고 있어야 한다고 생각을 했고 해당 메서드를 2가지 메서드로 분리하기로 했다.
- Request를 검증한다.
- 로그인 또는 회원가입 후 토큰을 발급한다.
로그인시에 반드시 이름을 받는것이 요구사항이었으므로 메서드에서 Token을 파싱해서 이름이 포함되어 있는지 확인하고 만약에 그렇다면 회원가입이 되는 것이다.
지금와서 보니 메서드명도 잘못된거 같다.
public AppleCredential validateRequest(AppleSignUpRequestDto signUpRequest) {
AppleCredential appleUserInfo = appleIDTokenValidator.extractAppleUserinfoFromIDToken(signUpRequest.getIdentityToken());
if(signUpRequest.getName() == null || signUpRequest.getName().isEmpty()) {
throw new ApiException(AppleTokenStatus.INVALID_SIGNUP_FORM);
}
appleUserInfo.setName(signUpRequest.getName());
return appleUserInfo;
}
public TokenDto signInOrUp(AppleCredential credential) {
Member member = MemberConverter.toEntity(credential);
AppleOAuth2UserInfo userInfo = new AppleOAuth2UserInfo(credential);
memberService.signInOrUp(member);
TokenDto token = tokenHelper.createToken(userInfo);
return token;
}
테스트 코드 작성
위와 같이 메서드의 역할을 분리해놓고 나니 테스트 작성이 좀 더 수월해졌다.
@Test
@DisplayName("유효한 토큰으로 Apple User Info 받아올 수 있는지를 검증한다.")
void validateRequestWithValidTokenReturnsUserInfo() {
// 유효한 토큰과 이름으로 validateRequest 메서드를 호출하고 결과를 검증
AppleCredential result = mockAppleService.validateRequest(validSignUpRequest);
// 반환된 AppleUserInfo 객체가 기대한 이름과 이메일을 가지고 있는지 확인
assertEquals("John Doe", result.getName());
}
@Test
@DisplayName("이름이 없는 경우 회원가입 ApiException이 발생하는지 검증한다.")
void validateRequestWithInvalidNameThrowsApiException() {
// 이름이 유효하지 않을 때 validateRequest 메서드를 호출하면 ApiException이 발생하는지 확인
assertThrows(ApiException.class, () -> {
mockAppleService.validateRequest(invalidNameSignUpRequest);
});
}
이름이 있는 경우와 없는 경우를 테스트하는 코드와
@Test
@DisplayName("signInOrUp 메서드를 통해서 생성될 수 있는 회원의 이메일은 중복되지 않아야 한다.")
public void signInOrUp() {
// given
AppleCredential appleUserInfo = AppleCredential.builder()
.issuer("yourIssuer")
.name("yourName")
.sub("yourUniqueIdentifier")
.clientId("yourClientId")
.expiryTime("yourExpiryTime")
.issuingTime("yourIssuingTime")
.nonce("yourNonce")
.email("yourEmail")
.emailVerified(true)
.build();
// when
appleService.signInOrUp(appleUserInfo);
appleService.signInOrUp(appleUserInfo);
List<Member> members = memberRepository.findByEmail(appleUserInfo.getEmail());
// then
assertEquals(members.size(), 1);
}
중복된 유저가 없게 로그인/회원가입 처리를 하는 로직이 깔끔하게 구분이 되었다.
'Spring > Error & Review' 카테고리의 다른 글
QueryDSL Join Error 해결 (0) | 2024.03.30 |
---|---|
[겜문철] 개발 Log (0) | 2024.03.23 |
통합 테스트의 필요성 (0) | 2024.03.20 |
Spring에서 JWT을 좀 더 자세히 알아보자! (0) | 2024.02.24 |
Spring Security Oauth2 Redirect를 에러 메시지로 전환 (0) | 2024.02.24 |