⏱️ 오늘의 일정 ⏱️
9:00 ~ 10:00 - 알고리즘 코드 카타
10:00 ~ 12:30 - 팀 프로젝트
12:30 ~ 14:00 - 점심시간
14:00 ~ 16:00 - 팀 프로젝트 발표
16:00 ~ 18:00 - 팀 프로젝트 정리
18:00 ~ 19:00 - 저녁 시간
19:00 ~ 21:00 - 스프링 심화 강의
21:00 ~ 22:00 - TIL 작성
스프링 강의 - 소셜 로그인
감사하게도 저녁 7시에 강의를 올려주셨기 때문에 찍먹해보겠다는 마음가짐으로 강의를 틀었다~
4시간 좀 안되는 짧은 분량이니 내일 오전 중으로 끝내겠다 ㅋㅋ
소셜 로그인 부분은 이미 생고생 하면서 해봤던 거라 편한 마음으로 (1.5배속으로)강의를 들었다.
그래도 카카오 로그인은 안 해봤던 것이기 때문에 나름 새로운 부분도 있었다.
RestTemplateBuilder
RestTemplate을 Builder로 생성할 수 있다.
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
// RestTemplate 으로 외부 API 호출 시 일정 시간이 지나도 응답이 없을 때
// 무한 대기 상태 방지를 위해 강제 종료 설정
.setConnectTimeout(Duration.ofSeconds(5)) // 5초
.setReadTimeout(Duration.ofSeconds(5)) // 5초
.build();
}
여기서 Timeout을 설정할 수도 있다.
게다가 Bean으로 등록하니 모든 레스트 템플릿에서 공통된 설정을 가진 restTemplate을 사용할 수 있다.
이런 부분은 개인, 팀 프로젝트에 적용해도 괜찮겠다~
회원이 아닐 경우 회원가입
private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
// DB 에 중복된 Kakao Id 가 있는지 확인
Long kakaoId = kakaoUserInfo.getId();
User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);
if (kakaoUser == null) {
// 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
String kakaoEmail = kakaoUserInfo.getEmail();
User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
if (sameEmailUser != null) {
kakaoUser = sameEmailUser;
// 기존 회원정보에 카카오 Id 추가
kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
} else {
// 신규 회원가입
// password: random UUID
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
// email: kakao email
String email = kakaoUserInfo.getEmail();
kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
}
userRepository.save(kakaoUser);
}
return kakaoUser;
}
직접 구현했던 것과 다른 것이 하나 보인다.
우리 프로젝트에서는 회원가입 되어있는지 볼 때 무조건 전체 email을 조회해 찾았는데 여기서는 먼저 kakaoId(카카오에서 주는 고유 id) 칼럼을 조회해본다.
로그인 할 때마다 모든 email을 검사하지 않을 것이기 때문에 이 쪽이 더 효율적인 코드 같다.
그러나 지금은 한 User Entity에 kakaoId를 추가해 주었는데, 구글 등 다른 소셜 로그인을 붙인다면 계속 User 테이블을 수정해야 하는 이 방법은 좋지 않아 보인다.
네이버 소셜 로그인 가이드에서 기존 User를 사용하면서, 네이버에만 존재하는 정보들에 대한 Entity를 따로 만들던데, 난 이 방식이 더 와닿았다!
한 Enitity를 만드는 것 보단 다른 테이블을 만드는 것이 더 유연하단 생각이 든다.
스프링 강의 - 테스트
단위 테스트
Mockito에 대한 내용도 있었는데 이전에 쌩고생하면서 적용 해본 부분이라 그런지 다 아는 내용이었다.
이전에 Mockito를 적용했던 내용은 아래 참고!
통합 테스트
두 개 이상의 모듈이 연결된 상태를 테스트 한다. 모듈 간의 연결에서 발생하는 에러 검증이 가능하다.
기본 단위 테스트 때는 Spring이 동작하지 않는다.
@SpringBootTest 애너테이션을 테스트 클래스에 사용해 스프링이 동작되도록 해준다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS) 애너테이션을 통해 테스트 라이프사이클을 클래스 단위로 설정한다.
컨트롤러는 어떻게 테스트?
컨트롤러 테스트의 제일 큰 문제점은, 스프링 시큐리티가 작동한다는 것이다.
스프링 시큐리티를 작동하지 않게 하기 위해 Mock필터를 만들어야 한다.
public class MockSpringSecurityFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
SecurityContextHolder.getContext()
.setAuthentication((Authentication) ((HttpServletRequest) req).getUserPrincipal());
chain.doFilter(req, res);
}
@Override
public void destroy() {
SecurityContextHolder.clearContext();
}
}
컨트롤러 테스트에서 가장 중요한 것은 @WebMvcTest 애너테이션을 통해 가짜 Mvc 환경을 만들어 주는 것이다.
@WebMvcTest(
controllers = {UserController.class, ProductController.class},
excludeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = WebSecurityConfig.class
)
}
)
class UserProductMvcTest {
private MockMvc mvc;
private Principal mockPrincipal;
@Autowired
private WebApplicationContext context;
@Autowired
private ObjectMapper objectMapper;
@MockBean
UserService userService;
@MockBean
KakaoService kakaoService;
@MockBean
ProductService productService;
@MockBean
FolderService folderService;
@BeforeEach
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity(new MockSpringSecurityFilter()))
.build();
}
private void mockUserSetup() {
// Mock 테스트 유져 생성
String username = "sollertia4351";
String password = "robbie1234";
String email = "sollertia@sparta.com";
UserRoleEnum role = UserRoleEnum.USER;
User testUser = new User(username, password, email, role);
UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
}
@Test
@DisplayName("로그인 Page")
void test1() throws Exception {
// when - then
mvc.perform(get("/api/user/login-page"))
.andExpect(status().isOk())
.andExpect(view().name("login"))
.andDo(print());
}
@Test
@DisplayName("회원 가입 요청 처리")
void test2() throws Exception {
// given
MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
signupRequestForm.add("username", "sollertia4351");
signupRequestForm.add("password", "robbie1234");
signupRequestForm.add("email", "sollertia@sparta.com");
signupRequestForm.add("admin", "false");
// when - then
mvc.perform(post("/api/user/signup")
.params(signupRequestForm)
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/api/user/login-page"))
.andDo(print());
}
@Test
@DisplayName("신규 관심상품 등록")
void test3() throws Exception {
// given
this.mockUserSetup();
String title = "Apple <b>아이폰</b> 14 프로 256GB [자급제]";
String imageUrl = "https://shopping-phinf.pstatic.net/main_3456175/34561756621.20220929142551.jpg";
String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=34561756621";
int lPrice = 959000;
ProductRequestDto requestDto = new ProductRequestDto(
title,
imageUrl,
linkUrl,
lPrice
);
String postInfo = objectMapper.writeValueAsString(requestDto);
// when - then
mvc.perform(post("/api/products")
.content(postInfo)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.principal(mockPrincipal)
)
.andExpect(status().isOk())
.andDo(print());
}
}
그리고 mvc.perform에서 api 호출을 해볼 수 있다는 것.
'공부 기록 > 내배캠Java_5기' 카테고리의 다른 글
[내배캠][TIL] 40일 차 - 목요일, 테스트라는 이름의 노가다 (2) | 2024.06.14 |
---|---|
[내배캠][TIL] 39일 차 - 수요일, 스프링 심화 주차 시작 (0) | 2024.06.12 |
[내배캠][TIL] 37일 차 - 월요일, 팀 프로젝트 마무리를 하며 (1) | 2024.06.10 |
[내배캠][TIL] 36일 차 - 금요일, 소셜 로그인 (0) | 2024.06.08 |
[내배캠][TIL] 35일 차 - 수요일, 자잘한 이슈들 (0) | 2024.06.05 |