WEB/Java Test

TestContainers

Tony Lim 2022. 11. 11. 14:03
728x90

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/

 

Postgres Module - Testcontainers for Java

 

www.testcontainers.org

여기 종류별로 튜토리얼이 존재한다.


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클래스에 매핑해서 사용해줘야한다.

 

 

 

 

 

728x90

'WEB > Java Test' 카테고리의 다른 글

운영 이슈 + 아키텍처 테스트  (0) 2022.11.14
Mockito  (0) 2022.11.11
JUnit5  (0) 2022.11.10