Controller, Service, Repository 각 계층의 단위/슬라이스 테스트를 진행하였고 이후 통합테스트를 진행한다.
FullIntegrationTest 클래스의 경우 지난 Repository 테스트 포스팅에서 활용했던 AbstractIntegrationTest를 똑같이 상속받아 사용한다.
통합테스트에서 이용될 것이기에 @SpringBootTest 어노테이션을 붙이고, MockMvc를 사용하기 위한 @AutoConfigureMockMvc어노테이션을 붙여 작성한다. ( MockMvc 접근제어를 proteceted로 설정해 상속받은 클래스들에서 해당 빈을 사용할 수 있도록 함 )
토큰을 생성하는데에 활용되는 jwt secret key의 경우 JwtTokenProvider에@Value("${jwt.secret}")로 정의되어있다. 테스트 환경에서의 시크릿 키 주입은 환경변수에 정의되어있는 값보다 @SpringBootTest properties로 작성된 값을 먼저 읽어온다. ( src/test/resources/application-test.yml 파일을 만들고 @ActiveProfiles("test")를 사용하는 방식도 가능함 )
실제 운영 환경의 시크릿 키는 보안상 노출되면 안 되기에 테스트 환경에서 별도의 키를 사용하여 안전하게 검증하도록 한다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"jwt.secret=jwtsecretjwtsecretjwtsecretjwtsecretjwtsecret"
})
@AutoConfigureMockMvc
public class FullIntegrationTest extends AbstractIntegrationTest {
@Autowired
protected MockMvc mockMvc;
}
"회원가입" -> "로그인" -> "판매자용 상품등록" 통합시나리오 테스트를 진행할 것이다.
특정 권한이 필요한 api이기 때문에 회원가입시 역할을 잘 등록해주고 User가 잘 등록되었는지 직접 검증한다.
이후, 로그인하여 생성된 액세스토큰을 받을 것인데 이 과정에서 쿠키에 리프레시토큰도 잘 담겼는지 확인을 한다.
request header에 액세스토큰을 넣어주고 최종적으로 상품 등록 요청을 보낸다. 토큰에서 역할을 파싱하고 판매자 권한임을 확인 후에 등록이 정상적으로 잘 된 것을 확인하게 된다.
public class SellerProductIntegrationTest extends FullIntegrationTest {
@Autowired
private ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private ProductRepository productRepository;
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("회원가입 후 로그인하여 판매자가 상품을 등록하는 통합 시나리오")
void FullRegisterProductScenarioTest() throws Exception {
SignupRequestDto signupRequestDto = SignupRequestDto.builder().name("tester")
.email("test@test.com")
.gender("여성")
.password("password123").roles(Set.of("판매자")).build();
mockMvc.perform(
post("/api/user/signup").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(
objectMapper.writeValueAsString(signupRequestDto))).andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("회원가입이 완료되었습니다."));
assertThat(userRepository.findByEmail("test@test.com")).isPresent();
LoginRequestDto loginRequestDto = LoginRequestDto.builder()
.email("test@test.com").password("password123").build();
MvcResult loginResult = mockMvc.perform(
post("/api/user/login").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(
objectMapper.writeValueAsString(loginRequestDto))).andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("로그인이 완료되었습니다."))
.andExpect(cookie().exists("refreshToken"))
.andExpect(cookie().httpOnly("refreshToken", true)).andReturn();
String loginResponseContent = loginResult.getResponse().getContentAsString();
JsonNode root = objectMapper.readTree(loginResponseContent);
String accessToken = root.path("result").asText();
ProductRequestDto productRequestDto = ProductRequestDto.builder()
.name("테스트 상품")
.brandName("브랜드 이름")
.image("url")
.stockQuantity(300)
.price(BigDecimal.valueOf(10000))
.build();
mockMvc.perform(
post("/api/seller/product/register").with(csrf()).header("Authorization", accessToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(productRequestDto))).andDo(print())
.andExpect(status().isOk()).andExpect(jsonPath("$.message").value("상품 등록이 완료되었습니다."));
List<Product> products = productRepository.findAll();
assertThat(products).anyMatch(p -> p.getName().equals("테스트 상품"));
}
}