스프링 부트 3.2에서 docker-compose.yml와 연결하여 TestContainers를 설정하는 방법 (with Mysql)

 

개요

로컬 혹은 CI 서버에서 실제 운영 환경과 같은 디비를 사용하여 통합 테스트를 하고 싶어 TestContainers 설정을 적용했다.

세부 설정은 docker-compose.yml로 관리하는게 편하여 이를 import하는 방식으로 구현했다.

 

환경

스프링 부트 3.2.6

Gradle 8.8

자바 17

 

설정

gradle에 라이브러리 추가

ext {
    testcontainersVersion = "1.19.0"
}

dependencies {
    testImplementation "org.springframework.boot:spring-boot-testcontainers"
    testImplementation 'org.testcontainers:mysql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

dependencyManagement {
    imports {
        mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
    }
}

 

 

src/test/resources
ㄴ application-test.yml
ㄴ docker-compse.yml
ㄴ schema.sql

 

docker-compose.yml

version: "3.8"

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: container
      MYSQL_CHARSET: utf8mb4
      MYSQL_COLLATION: utf8mb4_unicode_ci
      MYSQL_ROOT_PASSWORD: password
    ports:
      - 3306
    volumes:
      - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql
    networks:
      - test_network

networks:
  test_network:
    driver: bridge
    name: test_network
  • 레디스 등 다른 서비스를 띄워야 한다면 일반적인 docker-compose.yml 하는 것처럼 추가하면 된다.
  • DB에 DDL을 실행시키기 위해 docker volumes 설정
  • 네트워크를 지정하지 않으면 TestContainer를 가동시킬때마다 네트워크를 별도로 생성하게 된다.
    • 네트워크 풀이 가득찰 경우 아래와 같은 에러가 발생하여 실행이 안될수도 있으니 네트워크를 별도로 지정해주자!

14:02:57.541 [Test worker] ERROR tc.docker:24.0.2 -- Log output from the failed container: Network epceaxzqtavu_default Creating Network epceaxzqtavu_default Error failed to create network epceaxzqtavu_default: Error response from daemon: all predefined address pools have been fully subnetted

 

  • name 까지 지정해줘야 한다. 지정해 주지 않으면 rhp9z3rirgm1_test_network 처럼 앞에 랜덤 접두사가 붙은 네트워크가 실행시 마다 종종 추가된다.

 

schema.sql

DROP TABLE IF EXISTS table_name;

CREATE TABLE `table_name` {}

 

  • 필요한 DDL을 넣어 주자

 

 

IntegrationTest.java

@ActiveProfiles("test")
@SpringBootTest
@ContextConfiguration(initializers = IntegrationTest.IntegrationTestInitializer.class)
public class IntegrationTest {

    private static final ComposeContainer DOCKER_COMPOSE =
        new ComposeContainer(new File("src/test/resources/docker-compose.yml"))
            .withExposedService("mysql", 3306, Wait.forLogMessage(".*mysqld: ready for connections.*", 2));

    @BeforeAll
    public static void setupContainers() {
        DOCKER_COMPOSE.start();
    }

    static class IntegrationTestInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(@NotNull ConfigurableApplicationContext applicationContext) {
            Map<String, String> properties = new HashMap<>();

            setDatabaseProperties(properties);

            TestPropertyValues.of(properties).applyTo(applicationContext);
        }

        private void setDatabaseProperties(Map<String, String> properties) {
            String rdbmsHost = DOCKER_COMPOSE.getServiceHost("mysql", 3306);
            Integer rdbmsPort = DOCKER_COMPOSE.getServicePort("mysql", 3306);
            properties.put("spring.datasource.url", "jdbc:mysql://" + rdbmsHost + ":" + rdbmsPort + "/container");
            properties.put("spring.datasource.username", "root");
            properties.put("spring.datasource.password", "password");
        }

    }
}

 

  • docker-compose.yml 가져오고 스프링 컨텍스트를 띄우기 전에 디비 설정 해준다음, TestContainers를 실행

 

    private static final ComposeContainer DOCKER_COMPOSE =
        new ComposeContainer(new File("src/test/resources/docker-compose.yml"))
            .withExposedService("mysql", 3306, Wait.forLogMessage(".*mysqld: ready for connections.*", 2));

 

  • 위 코드가 핵심이다. 처음에 DockerComposeContainer으로 했다가 에러가 떠서 ComposeContainer으로 변경했다. 내부 코드 까보니 DockerComposeContainers는 @Deprecated 되었다.
  • 컨테이너가 뜬 다음 테스트 코드가 실행 되야 하기 때문에 Wait을 설정해줘야 한다. 안그럼 실행을 할 수 없다. mysql 메시지 내용 찾느라 조금 헤멤..
  • 다른 서비스도 띄워야 한다면 .withExposedService 한줄 추가하고 IntegrationTestInitializer에서 해당 서비스 설정을 추가하면 된다.

 

public class DatabaseTests extends IntegrationTest {

    @Autowired
    private TestJpaRepository testJpaRepository;

    @Test
    void test() {
        TestEntity save = testJpaRepository.save(TestEntity.create(1L));
    }

}

 

  • 생성한 DDL에 맞는 엔티티 하나를 가지고 간단히 삽입 테스트를 했을때 에러가 뜨지 않으면 성공

 

 

추후 레디스 같은 다른 서비스도 추가하게 되면 글 내용을 수정하겠다!!

 

 

 

 

참고

https://java.testcontainers.org/modules/docker_compose/#compose-v2

https://danielme.com/2023/04/13/testing-spring-boot-docker-with-testcontainers-and-junit-5-mysql-and-other-images/

https://velog.io/@byulcode/Docker-compose%EB%A1%9C-Mysql-%EC%84%A4%EC%A0%95

 

https://jaehoney.tistory.com/222

 

↓↓↓ 아래 글이 많은 도움이 되었다. Shout out to dkswnkk..

https://dkswnkk.tistory.com/719

 

TestContainer로 통합 테스트 환경 구축하기

[개요] 통합 테스트 환경을 구축할 때, 데이터베이스와의 연동은 주요한 고려사항 중 하나이며, 테스트의 안정성과 신뢰성을 높이기 위해서는 실제 운영 환경과 유사한 데이터베이스 환경에서

dkswnkk.tistory.com