1. 개요
현재 소셜로그인은 관련 제공사의 확장에 대응하기 위해 다음과 같아 전략 패턴을 사용하고 있다.
private final List<OAuth2MemberStrategy> oAuth2MemberStrategies;
/**
* OAuth2 과정을 프론트 단에서 처리
* @param response
* @param oAuth2LoginDto
* @param provider
* @return
*/
public CheckActiveMemberDto oauth2Login(HttpServletResponse response, OAuth2LoginDto oAuth2LoginDto, OAuth2Provider provider) {
OAuth2UserInfo userInfo = generateOAuth2UserInfo(oAuth2LoginDto, provider);
String username = userInfo.provider() + " " + userInfo.id();
boolean existMember = memberRepository.existsByUsername(username);
Member member = createMember(provider, existMember, username, userInfo);
makeJWTs(member, response);
return createResponse(response, member);
}
...
private OAuth2UserInfo generateOAuth2UserInfo(OAuth2LoginDto oAuth2LoginDto, OAuth2Provider provider) {
//소셜 로그인 전략 설정
log.info("[{}] 소셜 로그인 전략 설정 및 정보 추출", Thread.currentThread().getStackTrace()[1].getMethodName());
return oAuth2MemberStrategies.stream()
.filter(oAuth2MemberStrategy -> oAuth2MemberStrategy.isTarget(provider))
.findAny()
.orElseThrow(() -> new CustomException(StatusCode.OAUTH2_LOGIN_FAILURE))
.getOAuth2UserInfo(oAuth2LoginDto);
}
List를 사용하여 스프링부트의 OAuth2MemberStrategy 인터페이스를 구현한 빈을 조회하고, 이에 맞는 전략을 찾아 회원 정보를 조회한다.
@Test
@DisplayName("카카오 로그인 - 관련 기능들의 정상 동작 및 해당 dto의 성공적 반환, 새 회원")
void oauth2LoginV2WithNewMember() {
//given
MockHttpServletResponse response = new MockHttpServletResponse();
OAuth2LoginDto oAuth2LoginDto = OAuth2LoginDto.builder()
.id("1234")
.nickname("sample_nickname")
.build();
OAuth2Provider kakaoProvider = OAuth2Provider.KAKAO;
OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.builder()
.id("1234")
.nickname("sample_nickname")
.provider(OAuth2Provider.KAKAO)
.build();
String sampleAccess = "sample_access_token";
String sampleRefresh = "sample_refresh_token";
given(jwtUtil.createJwt(any(LoginCreateJwtDto.class), eq("access"))).willReturn(sampleAccess);
given(jwtUtil.createJwt(any(LoginCreateJwtDto.class), eq("refresh"))).willReturn(sampleRefresh);
given(memberRepository.existsByUsername(anyString())).willReturn(false);
//when
CheckActiveMemberDto result = oAuth2Service.oauth2Login(response, oAuth2LoginDto, kakaoProvider);
//then
//토큰이 헤더에 정상적으로 들어가야 함
assertThat(response.getHeader("Authorization")).isEqualTo("Bearer " + sampleAccess);
assertThat(response.getHeader("refresh_token")).isEqualTo("Bearer " + sampleRefresh);
//새 회원이므로 isActivate는 false여야 함
assertThat(result.nickname()).isEqualTo("sample_nickname");
assertThat(result.isActivate()).isFalse();
}
java.lang.NullPointerException: Cannot invoke "java.util.List.stream()" because "this.oAuth2MemberStrategies" is null
하지만 서비스, Strategy 모두 mock 객체이고, 이로 인해 로그인 전략을 찾는 부분에서 오류가 발생하게 된다. 이를 해결하기 위해 Mock 객체를 등록하고, 이들이 List에 담겨 사용할 수 있어야 한다.
2. @Mock vs @Spy
@Spy는 @Mock과 달리 실제 인스턴스를 생성하여 Mocking을 수행한다. 에러 메시지에서 List가 실제 인스턴스여야 stream 기능을 정상적으로 수행할 수 있으므로 Spy를 사용해야 한다.
@Spy
private List<OAuth2MemberStrategy> strategyList = new ArrayList<>();
@Mock
private KakaoOAuth2LoginStrategy kakaoOAuth2LoginStrategy;
@Mock
private AppleOAuth2LoginStrategy appleOAuth2LoginStrategy;
@BeforeEach
public void init(){
strategyList.add(kakaoOAuth2LoginStrategy);
strategyList.add(appleOAuth2LoginStrategy);
}
실제 리스트를 만들고, 두 전략의 Mock 객체를 만들어 넣어준다.
OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.builder()
.id("1234")
.nickname("sample_nickname")
.provider(OAuth2Provider.KAKAO)
.build();
given(kakaoOAuth2LoginStrategy.isTarget(OAuth2Provider.KAKAO)).willReturn(true);
given(kakaoOAuth2LoginStrategy.getOAuth2UserInfo(oAuth2LoginDto)).willReturn(oAuth2UserInfo);
서비스 계층의 단위 테스트이므로 전략에 대해서는 결과를 정의해준다.
소셜 로그인 관련 모든 테스트가 통과하는 것을 확인할 수 있었다.
3. 참고
https://developer-youngjun.tistory.com/6
Mockito : Mock 리스트를 주입하고 테스트 하기
상황 스프링을 사용하여 빈을 주입 받을때, 같은 타입(interface)을 구현한 빈들을 아래와 같이 컬렉션으로 주입 받아 사용하는 경우가 있다. public interface Validator { void validate(Order order); } @Service publ
developer-youngjun.tistory.com
https://coco-log.tistory.com/194
Spy와 Mock의 차이. 그리고 Spy 사용
Mock과 Spy는 테스트 더블(대역)이다. Test Double은 테스트를 목적으로 프로덕션 오브젝트를 대체하는 오브젝트를 뜻한다. ‘Test Double’이라는 말 때문에 처음에는 잘 이해가 되지 않았는데 영어권
coco-log.tistory.com
'Proj > Tripot' 카테고리의 다른 글
TourAPI를 이용하여 축제 데이터 수집하기 (0) | 2025.04.05 |
---|---|
k6을 이용한 성능 테스트 (1) | 2025.03.16 |
모니터링 구축(feat. Prometheus, Grafana) (0) | 2025.03.11 |
MySQL 백업하기 (0) | 2025.03.05 |
Trouble Shooting - nginx 용량 늘리기(feat. 스토리 등록 오류) (0) | 2025.03.04 |