[Spring] Integration 테스트

홍서영·2025년 12월 25일

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("테스트 상품"));
    }
}
profile
백엔드 개발자

0개의 댓글