add testcode

This commit is contained in:
ondal
2025-02-14 23:54:34 +09:00
parent 2f672a8ea5
commit 17d8591ae9
107 changed files with 7513 additions and 36 deletions
@@ -1,8 +1,8 @@
package com.unicorn.lifesub.member.config;
import com.unicorn.lifesub.member.config.jwt.CustomUserDetailsService;
import com.unicorn.lifesub.member.config.jwt.JwtTokenProvider;
import com.unicorn.lifesub.member.config.jwt.JwtAuthenticationFilter;
import com.unicorn.lifesub.member.config.jwt.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -2,12 +2,17 @@ package com.unicorn.lifesub.member.controller;
import com.unicorn.lifesub.common.dto.ApiResponse;
import com.unicorn.lifesub.common.dto.JwtTokenDTO;
import com.unicorn.lifesub.member.dto.*;
import com.unicorn.lifesub.common.exception.BusinessException;
import com.unicorn.lifesub.common.exception.InfraException;
import com.unicorn.lifesub.member.dto.LoginRequest;
import com.unicorn.lifesub.member.dto.LogoutRequest;
import com.unicorn.lifesub.member.dto.LogoutResponse;
import com.unicorn.lifesub.member.service.MemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -24,8 +29,18 @@ public class MemberController {
@Operation(summary = "로그인", description = "사용자 ID와 비밀번호로 로그인합니다.")
@PostMapping("/login")
public ResponseEntity<ApiResponse<JwtTokenDTO>> login(@Valid @RequestBody LoginRequest request) {
JwtTokenDTO response = memberService.login(request);
return ResponseEntity.ok(ApiResponse.success(response));
try {
JwtTokenDTO response = memberService.login(request);
return ResponseEntity.ok(ApiResponse.success(response));
} catch (BusinessException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error(e.getErrorCode()));
} catch (InfraException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error(e.getErrorCode()));
}
}
@Operation(summary = "로그아웃", description = "현재 로그인된 사용자를 로그아웃합니다.")
@@ -55,4 +55,8 @@ public class MemberEntity extends BaseTimeEntity {
.roles(member.getRoles())
.build();
}
public void updateUserName(String userName) {
this.userName = userName;
}
}
@@ -33,15 +33,15 @@ public class MemberServiceImpl implements MemberService {
MemberEntity member = memberRepository.findByUserId(request.getUserId())
.orElseThrow(() -> new InfraException(ErrorCode.MEMBER_NOT_FOUND));
if (!passwordEncoder.matches(request.getPassword(), member.getPassword())) {
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
}
// 사용자의 권한 정보 생성
Collection<? extends GrantedAuthority> authorities = member.getRoles().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
if (!passwordEncoder.matches(request.getPassword(), member.getPassword())) {
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
}
return jwtTokenProvider.createToken(member, authorities);
}
@@ -0,0 +1,144 @@
package com.unicorn.lifesub.member.test.e2e;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unicorn.lifesub.common.dto.ApiResponse;
import com.unicorn.lifesub.common.dto.JwtTokenDTO;
import com.unicorn.lifesub.member.dto.LoginRequest;
import com.unicorn.lifesub.member.dto.LogoutRequest;
import com.unicorn.lifesub.member.repository.jpa.MemberRepository;
import com.unicorn.lifesub.member.test.e2e.config.TestContainerConfig;
import com.unicorn.lifesub.member.test.e2e.support.TestDataManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("e2e-test")
class MemberE2ETest extends TestContainerConfig {
@Autowired
private WebApplicationContext context;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TestDataManager testDataManager;
@Autowired
private MemberRepository memberRepository;
private WebTestClient webClient;
@Value("${test.user.id}")
private String TEST_USER_ID;
@Value("${test.user.password}")
private String TEST_PASSWORD;
@BeforeEach
void setUp() {
webClient = MockMvcWebTestClient
.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.configureClient()
.build();
memberRepository.deleteAll();
testDataManager.setupTestData();
}
@Test
@DisplayName("로그인 성공 시나리오")
void givenValidCredentials_whenLogin_thenSuccess() {
// Given
LoginRequest loginRequest = createLoginRequest(TEST_USER_ID, TEST_PASSWORD);
// When & Then
webClient.post().uri("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(loginRequest)
.exchange()
.expectStatus().isOk()
.expectBody(ApiResponse.class)
.value(response -> {
assertThat(response.getStatus()).isEqualTo(200);
JwtTokenDTO jwtToken = objectMapper.convertValue(response.getData(), JwtTokenDTO.class);
assertThat(jwtToken.getAccessToken()).isNotNull();
assertThat(jwtToken.getRefreshToken()).isNotNull();
});
}
@Test
@DisplayName("잘못된 비밀번호로 로그인 실패 시나리오")
void givenInvalidPassword_whenLogin_thenFail() {
// Given
LoginRequest loginRequest = createLoginRequest(TEST_USER_ID, "wrongpassword");
// When & Then
webClient.post().uri("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(loginRequest)
.exchange()
.expectStatus().isUnauthorized()
.expectBody(ApiResponse.class)
.value(response -> {
assertThat(response.getStatus()).isEqualTo(210);
assertThat(response.getMessage()).isEqualTo("Invalid credentials");
});
}
@Test
@DisplayName("로그인 후 로그아웃 시나리오")
void givenValidToken_whenLogout_thenSuccess() {
// Given - 먼저 로그인
LoginRequest loginRequest = createLoginRequest(TEST_USER_ID, TEST_PASSWORD);
ApiResponse loginResponse = webClient.post().uri("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(loginRequest)
.exchange()
.expectStatus().isOk()
.expectBody(ApiResponse.class)
.returnResult()
.getResponseBody();
JwtTokenDTO tokenDTO = objectMapper.convertValue(loginResponse.getData(), JwtTokenDTO.class);
// When - 로그아웃
LogoutRequest logoutRequest = new LogoutRequest();
logoutRequest.setUserId(TEST_USER_ID);
// Then
webClient.post().uri("/api/auth/logout")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + tokenDTO.getAccessToken())
.bodyValue(logoutRequest)
.exchange()
.expectStatus().isOk()
.expectBody(ApiResponse.class)
.value(response -> {
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getMessage()).contains("Success");
});
}
private LoginRequest createLoginRequest(String userId, String password) {
LoginRequest request = new LoginRequest();
request.setUserId(userId);
request.setPassword(password);
return request;
}
}
@@ -0,0 +1,25 @@
// TestContainerConfig.java
package com.unicorn.lifesub.member.test.e2e.config;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
public class TestContainerConfig {
@Container
static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:13.2-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop");
}
}
@@ -0,0 +1,68 @@
// TestDataManager.java
package com.unicorn.lifesub.member.test.e2e.support;
import com.unicorn.lifesub.member.repository.entity.MemberEntity;
import com.unicorn.lifesub.member.repository.jpa.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
@Component
@RequiredArgsConstructor
public class TestDataManager {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
@Value("${test.user.id}")
private String TEST_USER_ID;
@Value("${test.user.password}")
private String TEST_PASSWORD;
@Value("${test.user.name}")
private String TEST_USER_NAME;
@Transactional
public void setupTestData() {
if (memberRepository.count() == 0) {
// 기본 테스트 사용자 생성
createTestUser();
// 추가 테스트 데이터 생성
createAdditionalTestData();
}
}
private void createTestUser() {
Set<String> userRoles = new HashSet<>();
userRoles.add("USER");
MemberEntity testUser = MemberEntity.builder()
.userId(TEST_USER_ID)
.userName(TEST_USER_NAME)
.password(passwordEncoder.encode(TEST_PASSWORD)) // 암호화된 비밀번호 저장
.roles(userRoles)
.build();
memberRepository.save(testUser);
}
private void createAdditionalTestData() {
// 계정 잠금 테스트용 사용자
Set<String> userRoles = new HashSet<>();
userRoles.add("USER");
MemberEntity lockTestUser = MemberEntity.builder()
.userId("locktest")
.userName("Lock Test User")
.password(passwordEncoder.encode("lockpass"))
.roles(userRoles)
.build();
memberRepository.save(lockTestUser);
}
}
@@ -0,0 +1,23 @@
package com.unicorn.lifesub.member.test.integration.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@Profile("integration-test")
public class TestSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
@@ -0,0 +1,179 @@
package com.unicorn.lifesub.member.test.integration.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unicorn.lifesub.common.dto.JwtTokenDTO;
import com.unicorn.lifesub.member.controller.MemberController;
import com.unicorn.lifesub.member.dto.LoginRequest;
import com.unicorn.lifesub.member.dto.LogoutRequest;
import com.unicorn.lifesub.member.dto.LogoutResponse;
import com.unicorn.lifesub.member.service.MemberService;
import com.unicorn.lifesub.member.test.integration.config.TestSecurityConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MemberController 통합 테스트 클래스
* 회원 인증 관련 API의 요청/응답을 검증합니다.
*
* @author Tera
* @version 1.0
*/
@WebMvcTest(MemberController.class)
@Import({MemberControllerIntegrationTest.TestConfig.class, TestSecurityConfig.class})
@ActiveProfiles("integration-test")
class MemberControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private MemberService memberService;
/**
* 테스트를 위한 설정 클래스입니다.
* Controller와 의존성들을 직접 등록하여 테스트 환경을 구성합니다.
*
* @implNote 1. Controller와 필요한 빈들을 직접 등록하여 의존성을 명확히 합니다.
* 2. Service는 Mockito를 통해 mock 객체로 대체하여 테스트를 격리합니다.
*/
@Configuration
static class TestConfig implements WebMvcConfigurer {
/**
* MemberController 빈을 등록합니다.
* memberService()를 통해 Mock 객체를 주입받아 Controller를 생성합니다.
*
* @return MemberController 인스턴스
*/
@Bean
public MemberController memberController() {
return new MemberController(memberService());
}
/**
* MemberService Mock 객체를 생성하여 반환합니다.
* 실제 Service 구현체 대신 Mock 객체를 사용하여 테스트를 격리합니다.
*
* @return MemberService Mock 객체
*/
@Bean
public MemberService memberService() {
return mock(MemberService.class);
}
}
/**
* 로그인 성공 케이스를 테스트합니다.
* 올바른 사용자 ID와 비밀번호로 요청 시 JWT 토큰이 정상적으로 발급되는지 검증합니다.
*
* @throws Exception MockMvc 수행 중 발생할 수 있는 예외
*/
@Test
@DisplayName("로그인 성공 테스트")
void loginSuccess() throws Exception {
// Given: 테스트에 필요한 요청 데이터와 응답 객체를 준비
LoginRequest request = new LoginRequest();
request.setUserId("testUser");
request.setPassword("testPass");
JwtTokenDTO tokenDTO = JwtTokenDTO.builder()
.accessToken("test-access-token")
.refreshToken("test-refresh-token")
.build();
given(memberService.login(any(LoginRequest.class))).willReturn(tokenDTO);
// When & Then: API 호출 결과 검증
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.data.accessToken").value("test-access-token"))
.andExpect(jsonPath("$.data.refreshToken").value("test-refresh-token"));
}
/**
* 로그인 실패 케이스를 테스트합니다.
* 잘못된 형식의 요청 데이터로 API 호출 시 적절한 에러 응답이 반환되는지 검증합니다.
*
* @throws Exception MockMvc 수행 중 발생할 수 있는 예외
*/
@Test
@DisplayName("로그인 실패 테스트 - 잘못된 요청 형식")
void loginFailInvalidRequest() throws Exception {
// Given: 검증에 실패하도록 필수 필드가 비어있는 요청 객체 생성
LoginRequest request = new LoginRequest();
// When & Then: API 호출 결과 검증
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
/**
* 로그아웃 성공 케이스를 테스트합니다.
* 정상적인 로그아웃 요청 시 성공 응답이 반환되는지 검증합니다.
*
* @throws Exception MockMvc 수행 중 발생할 수 있는 예외
*/
@Test
@DisplayName("로그아웃 성공 테스트")
void logoutSuccess() throws Exception {
// Given: 테스트에 필요한 요청 데이터와 응답 객체를 준비
LogoutRequest request = new LogoutRequest();
request.setUserId("testUser");
LogoutResponse response = LogoutResponse.builder()
.message("로그아웃이 완료되었습니다.")
.build();
given(memberService.logout(any(LogoutRequest.class))).willReturn(response);
// When & Then: API 호출 결과 검증
mockMvc.perform(post("/api/auth/logout")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.data.message").value("로그아웃이 완료되었습니다."));
}
/**
* 로그아웃 실패 케이스를 테스트합니다.
* 잘못된 형식의 요청 데이터로 API 호출 시 적절한 에러 응답이 반환되는지 검증합니다.
*
* @throws Exception MockMvc 수행 중 발생할 수 있는 예외
*/
@Test
@DisplayName("로그아웃 실패 테스트 - 잘못된 요청 형식")
void logoutFailInvalidRequest() throws Exception {
// Given: 검증에 실패하도록 필수 필드가 비어있는 요청 객체 생성
LogoutRequest request = new LogoutRequest();
// When & Then: API 호출 결과 검증
mockMvc.perform(post("/api/auth/logout")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
}
@@ -0,0 +1,149 @@
package com.unicorn.lifesub.member.test.integration.repository;
import com.unicorn.lifesub.member.repository.entity.MemberEntity;
import com.unicorn.lifesub.member.repository.jpa.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
/**
* MemberRepository 통합 테스트
* TestContainers를 사용하여 실제 PostgreSQL DB와의 상호작용을 테스트합니다.
*/
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
@ActiveProfiles("integration-test")
class MemberRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:13.2-alpine")
.withDatabaseName("member")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
}
@Autowired
private MemberRepository memberRepository;
@Autowired
private TestEntityManager entityManager;
@Test
@DisplayName("회원 저장 및 조회 테스트")
void givenMemberEntity_whenSave_thenFindByUserId() {
// Given
String userId = "testUser";
Set<String> roles = new HashSet<>();
roles.add("USER");
MemberEntity member = MemberEntity.builder()
.userId(userId)
.userName("Test User")
.password("encodedPassword")
.roles(roles)
.build();
// When
entityManager.persistAndFlush(member);
entityManager.clear(); // 1차 캐시 클리어
// Then
Optional<MemberEntity> foundMember = memberRepository.findByUserId(userId);
assertThat(foundMember).isPresent();
assertThat(foundMember.get().getUserId()).isEqualTo(userId);
assertThat(foundMember.get().getUserName()).isEqualTo("Test User");
assertThat(foundMember.get().getRoles()).containsExactly("USER");
}
@Test
@DisplayName("존재하지 않는 회원 조회 테스트")
void givenNonExistentUserId_whenFindByUserId_thenReturnEmpty() {
// Given
String nonExistentUserId = "nonexistent";
// When
Optional<MemberEntity> result = memberRepository.findByUserId(nonExistentUserId);
// Then
assertThat(result).isEmpty();
}
@Test
@DisplayName("회원 정보 업데이트 테스트")
void givenExistingMember_whenUpdateInfo_thenSuccess() {
// Given
String userId = "testUser";
Set<String> roles = new HashSet<>();
roles.add("USER");
MemberEntity member = MemberEntity.builder()
.userId(userId)
.userName("Original Name")
.password("originalPassword")
.roles(roles)
.build();
entityManager.persistAndFlush(member);
entityManager.clear();
// When
MemberEntity foundMember = memberRepository.findByUserId(userId).get();
foundMember.updateUserName("Updated Name");
entityManager.persistAndFlush(foundMember);
entityManager.clear();
// Then
MemberEntity updatedMember = memberRepository.findByUserId(userId).get();
assertThat(updatedMember.getUserName()).isEqualTo("Updated Name");
}
@Test
@DisplayName("회원 삭제 테스트")
void givenExistingMember_whenDelete_thenCannotFind() {
// Given
String userId = "testUser";
Set<String> roles = new HashSet<>();
roles.add("USER");
MemberEntity member = MemberEntity.builder()
.userId(userId)
.userName("Test User")
.password("password")
.roles(roles)
.build();
entityManager.persistAndFlush(member);
entityManager.clear();
// When
MemberEntity foundMember = memberRepository.findByUserId(userId).get();
memberRepository.delete(foundMember);
entityManager.flush();
entityManager.clear();
// Then
Optional<MemberEntity> deletedMember = memberRepository.findByUserId(userId);
assertThat(deletedMember).isEmpty();
}
}
@@ -0,0 +1,87 @@
package com.unicorn.lifesub.member.test.integration.service;
import com.unicorn.lifesub.common.dto.JwtTokenDTO;
import com.unicorn.lifesub.member.config.jwt.JwtTokenProvider;
import com.unicorn.lifesub.member.dto.LoginRequest;
import com.unicorn.lifesub.member.dto.LogoutRequest;
import com.unicorn.lifesub.member.dto.LogoutResponse;
import com.unicorn.lifesub.member.repository.entity.MemberEntity;
import com.unicorn.lifesub.member.repository.jpa.MemberRepository;
import com.unicorn.lifesub.member.service.MemberServiceImpl;
import com.unicorn.lifesub.member.test.integration.config.TestSecurityConfig;
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.context.annotation.Import;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ActiveProfiles;
import java.util.Optional;
import static com.unicorn.lifesub.member.test.integration.support.TestDataFactory.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@Import(TestSecurityConfig.class)
@ActiveProfiles("integration-test")
class MemberServiceIntegrationTest {
@Mock
private MemberRepository memberRepository;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
private JwtTokenProvider jwtTokenProvider;
@InjectMocks
private MemberServiceImpl memberService;
@Test
@DisplayName("유효한 자격증명으로 로그인 성공")
void givenValidCredentials_whenLogin_thenReturnJwtToken() {
// Given
LoginRequest loginRequest = createLoginRequest();
MemberEntity memberEntity = createMemberEntity();
given(memberRepository.findByUserId(TEST_USER_ID)).willReturn(Optional.of(memberEntity));
given(passwordEncoder.matches(TEST_PASSWORD, memberEntity.getPassword())).willReturn(true);
given(jwtTokenProvider.createToken(eq(memberEntity), any())).willReturn(
new JwtTokenDTO("accessToken", "refreshToken"));
// When
JwtTokenDTO result = memberService.login(loginRequest);
// Then
assertThat(result).isNotNull();
assertThat(result.getAccessToken()).isEqualTo("accessToken");
assertThat(result.getRefreshToken()).isEqualTo("refreshToken");
verify(memberRepository).findByUserId(TEST_USER_ID);
verify(passwordEncoder).matches(TEST_PASSWORD, memberEntity.getPassword());
verify(jwtTokenProvider).createToken(eq(memberEntity), any());
}
@Test
@DisplayName("로그아웃 처리 성공")
void givenUserId_whenLogout_thenSuccess() {
// Given
LogoutRequest request = new LogoutRequest();
request.setUserId(TEST_USER_ID);
// When
LogoutResponse response = memberService.logout(request);
// Then
assertThat(response).isNotNull();
assertThat(response.getMessage()).contains("로그아웃");
}
}
@@ -0,0 +1,32 @@
package com.unicorn.lifesub.member.test.integration.support;
import com.unicorn.lifesub.member.repository.entity.MemberEntity;
import com.unicorn.lifesub.member.dto.LoginRequest;
import java.util.HashSet;
import java.util.Set;
public class TestDataFactory {
public static final String TEST_USER_ID = "testUser";
public static final String TEST_PASSWORD = "Password123!";
public static final String TEST_USERNAME = "Test User";
public static MemberEntity createMemberEntity() {
Set<String> roles = new HashSet<>();
roles.add("ROLE_USER");
return MemberEntity.builder()
.userId(TEST_USER_ID)
.userName(TEST_USERNAME)
.password(TEST_PASSWORD)
.roles(roles)
.build();
}
public static LoginRequest createLoginRequest() {
LoginRequest request = new LoginRequest();
request.setUserId(TEST_USER_ID);
request.setPassword(TEST_PASSWORD);
return request;
}
}
@@ -25,7 +25,7 @@ import static org.mockito.BDDMockito.given;
* Spring Security의 UserDetailsService 구현체 검증
*/
@ExtendWith(MockitoExtension.class)
class CustomUserDetailsServiceTest {
class CustomUserDetailsServiceUnitTest {
@InjectMocks
private CustomUserDetailsService userDetailsService;
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.when;
* JWT 토큰 제공자 테스트 클래스
* 토큰 생성, 검증, 파싱 등의 기능을 검증
*/
class JwtTokenProviderTest {
class JwtTokenProviderUnitTest {
private JwtTokenProvider jwtTokenProvider;
private static final String SECRET_KEY = "test-secret-key";
@@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Member 도메인 객체 테스트 클래스
* 도메인 객체의 생성 엔티티 변환 로직을 검증
*/
class MemberTest {
class MemberUnitTest {
// 테스트용 상수 정의
private static final String TEST_USER_ID = "testUser";
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.when;
* 주요 비즈니스 로직인 로그인/로그아웃 기능을 검증
*/
@ExtendWith(MockitoExtension.class)
class MemberServiceImplTest {
class MemberServiceImplUnitTest {
@InjectMocks
private MemberServiceImpl memberService;
@@ -0,0 +1,35 @@
# src/test/resources/application-e2e-test.yml
spring:
application:
name: member-service-test
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
# JWT 설정
jwt:
secret-key: testSecretKeyForE2ETestingPurposesOnlyDoNotUseInProduction
access-token-validity: 3600000 # 1시간
refresh-token-validity: 86400000 # 24시간
allowed-origins: http://localhost:3000
# 로깅 설정
logging:
level:
com.unicorn: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
# 테스트용 사용자 설정
test:
user:
id: testuser
password: Test1234!
name: Test User