"Spring IoC 컨테이너 컨텍스트가 올바르게 연결되었는지 확인 JDBC, 올바른 ORM, SQL 문법과 Hibernate 쿼리매핑등 데이터 접근 테스트 확인"
위에는 Spring 공식 문서에서 통합(integration-test)테스트를 하는 이유를 알려주는 문장입니다.
즉 Spring-bean들의 결합과 실제 DB 쿼리를 요구사항에 맞게 잘 던지고 있는가 이 두가지를 테스팅하는 것이 핵심이라고 할 수 있습니다.
통합 테스트를 하는 이유를 정리해보겠습니다.
단위 테스트로는 RequestMapping, Data Binding, Type Conversion, Validation 테스트의 한계가 있습니다. 왜냐하면 테스트 코드에서 짠 인스턴스값으로 컨트롤러에 넣어주기 때문입니다.
"따라서 실제 요청값과 다르기 때문에 배포환경과는 가깝지 않습니다."
"실제 서블릿 컨테이너이기 때문에 Rest API 로 테스트를 해 줘야 합니다. 이를 구현하는 두 가지 방법이 있습니다."
물론 현재 프로젝트는 리액티브서버는 아니지만, 확장성을 고려한다면 WebTestClient방식의 역량을 쌓는다면 리액티브서버에도 적용이 가능하니 해당방식을 채택하기로 함
통합 테스트를 위해서 Junit5에서는 여러 애플리케이션 컨텍스트 설정들을 제공합니다.
또한 testContainer를 사용해 실제환경과 똑같은 환경에서 테스트 할 수 있습니다.
해당 방식은 WebTestClient로 요청을 처리했을 때 필요합니다.
WebEnvironment.RANDOM_PORT
: Random한 port에서 병렬적으로 서버를 띄어서 테스트하는 방식 특정 포트에서만 해야하는 테스트가 없는경우 사용적절
WebEnvironment.DEFINED_PORT
: 정해져있는 port에서 서버를 띄운 테스트 방식, 만약 해당포트에 다음테스트를 해야한다면 기다렸다가 수행
특정 포트에서만 해야하는 테스트경우 사용적절 (GateWay등..)
테스트에 필요한 properties파일을 수정할 때 이용해줄 수 있습니다
예시 코드
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties") // 테스트 전용 properties 파일
public class MyServiceTest {
@Autowired
private MyService myService;
@Test
void testDatabaseConnection() {
// 테스트 코드 작성
// 예를 들어, 데이터베이스에 연결되어 있는지 확인하는 로직
}
}
DB뿐만 아니라 여러 인프라를 도커위에서 실행해볼 수 있어서 실제환경과 매우 유사하다.
code
@Testcontainers
@SpringBootTest
public class DatabaseIntegrationTest {
// PostgreSQL TestContainer 생성
@Container
public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("user")
.withPassword("password");
@BeforeAll
static void setUp() {
// TestContainer 시작 전에 필요한 설정 작업을 할 수 있습니다.
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}
@Test
void testDatabaseConnection() {
// 실제 데이터베이스 연결을 테스트합니다.
// TestContainer에 띄운 PostgreSQL에 대한 테스트를 작성합니다.
}
}
WebTestClient를 사용할 때 WebEnvironment.RANDOM_PORT를 사용하면 일반적으로 어노테이션 @Transactional을 사용한 트랜잭션이 불가능합니다.
따라서 AfterEach와 같은 방법으로 해당 문제를 해결해볼 수 있습니다.
code
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Transactional // 트랜잭션 관리
public class MyIntegrationTest {
@Autowired
private WebTestClient webTestClient;
@Autowired
private MyRepository myRepository;
@BeforeEach
void setUp() {
myRepository.deleteAll(); // 테스트 데이터 초기화
}
@Test
void testSomeEndpoint() {
webTestClient.get().uri("/some-endpoint")
.exchange()
.expectStatus().isOk();
// 트랜잭션 롤백 확인
}
@AfterEach
void cleanUp() {
myRepository.deleteAll(); // 테스트 후 DB 정리
}
}