database 랑 연관된 테스트를 진행하고 싶을 때 사용한다.
docker container 를 사용하게 되는데 TestContainer는 이를 수동으로 관리하는 번거로움 덜어준다.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.17.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.17.5</version>
<scope>test</scope>
</dependency>
junit 에서 지원하는 testcontainers를 써야한다
spring.datasource.url=jdbc:tc:postgresql:///studytest
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.jpa.hibernate.ddl-auto=create-drop
application.yaml 에서 jdbc url을 tc 를 통해서 주게되면 hostname이랑 port는 크게 중요하지 않게 된다.
testcontainer에서 제공하는 jdbc driver가 알아서 찾아주게 된다.
@Testcontainers
class StudyServiceTest {
@Mock MemberService memberService;
@Autowired StudyRepository studyRepository;
@Container
static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withDatabaseName("studytest");
@BeforeEach
void beforeEach() {
studyRepository.deleteAll();
}
@Test
void createNewStudy() {
// Given
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
Member member = new Member();
member.setId(1L);
member.setEmail("keesun@email.com");
Study study = new Study(10, "테스트");
given(memberService.findById(1L)).willReturn(Optional.of(member));
// When
studyService.createNewStudy(1L, study);
// Then
assertEquals(1L, study.getOwnerId());
then(memberService).should(times(1)).notify(study);
then(memberService).shouldHaveNoMoreInteractions();
}
@DisplayName("다른 사용자가 볼 수 있도록 스터디를 공개한다.")
@Test
void openStudy() {
// Given
StudyService studyService = new StudyService(memberService, studyRepository);
Study study = new Study(10, "더 자바, 테스트");
assertNull(study.getOpenedDateTime());
// When
studyService.openStudy(study);
// Then
assertEquals(StudyStatus.OPENED, study.getStatus());
assertNotNull(study.getOpenedDateTime());
then(memberService).should().notify(study);
}
}
@TestContainers 를 통해 확장팩을 주입받고 @Container를 통해 실제 db를 docker 에서 기동시켜주고 꺼주는것이다. (container life cycle을 관리해줌)
static 필드에 container를 써야 매번 테스트마다 기동하고 끄고 해서 시간을 잡아먹지 않는다.
매번 db container를 내리는것 보다 @BeforeEach를 통해서 테이블을 지우는 방법이 빠르다.
https://www.testcontainers.org/modules/databases/postgres/
여기 종류별로 튜토리얼이 존재한다.
TestContainer 기능 살펴보기
@Container
static GenericContainer postgreSQLContainer = new GenericContainer("postgres")
.withExposedPorts(5432)
.withEnv("POSTGRES_DB", "studytest");
이미지의 이름을 인자로주고 나머지는 환경변수로 줘서 db 컨테이너를 만들 수 있다.
5432 포트는 도커 컨네이터 내부에서 열어준 port이고 실제 host에서 매핑된 port는 랜덤이다.
알고싶다면 getMappedPort(5432) 로 알 수 있다.
컨테이너 기동시간이 오래걸리면 waitingFor () 메소드로 인자값으로 주어진 조건을 만족하기 전까지 테스트를 실행하지 않는다.
@BeforeAll
static void beforeAll() {
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log);
postgreSQLContainer.followOutput(logConsumer);
}
컨테이너 내부에서 찍히는 로그들을 확인할 수 있다. 인자로 준 log는 @Slf4j 에서 제공해준것이다.
컨테이너 정보를 스프링 테스트에서 참조하기
1. ApplicationContextInitializer 를 구현해 생성된 컨테이너에서 정보를 축출하여 Environment 에 넣어준다.
Environment = Property(application.properties,yml)에 접근하게 해주고 Profile 을 새팅및 접근하게 해준다.
ApplicationContextInitializer = 프로로그래밍을 초기화 할 때 사용할 수 있는 콜백 인터페이스로 , 특정 프로파일을 활성화 하거나, 프로퍼티 소스를 추가하는 등의 작업을 해준다.
static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
TestPropertyValues.of("container.port=" + postgreSQLContainer.getMappedPort(5432))
.applyTo(context.getEnvironment());
}
}
@ContextConfiguration(initializers = StudyServiceTest.ContainerPropertyInitializer.class)
class StudyServiceTest {
@Mock MemberService memberService;
@Autowired StudyRepository studyRepository;
@Value("${container.port}") int port;
host랑 매핑된 포트정보를 container.port=12345 를 property에 써주는 것과 같은 일을 하고 있다.
구현한후에 Test클래스에 매핑해서 사용해줘야한다.
'WEB > Java Test' 카테고리의 다른 글
운영 이슈 + 아키텍처 테스트 (0) | 2022.11.14 |
---|---|
Mockito (0) | 2022.11.11 |
JUnit5 (0) | 2022.11.10 |