💬 들어가기 전에
현재 개발중인 프로젝트의 환경은 다음과 같다.
- SpringBoot3
- JDK 21
- Postgresql, Redis Cluster
그리고 각 서비스를 모듈화하여 멀티 모듈 구조로 개발중에 있다.
테스트 환경은 테스트 컨테이너로 구성하였는데,
이번에 Redis
를 연동하면서 테스트 환경을 구성하는데에 어려움을 겪게 되어 그 과정을 기록하고자 한다.
⚙️ 기존 테스트 환경 설정
우선 기존 PostgreSQL
만 사용했을 때 설정해두었던 것들은 다음과 같다.
TestContainers
클래스에 테스트 컨테이너 관련 설정들을 하고,RestAssuredTest
클래스가 TestContainers
를 상속받아 테스트 관련 설정을 했다.
이후 실제 테스트 코드를 작성하는 클래스는 RestAssuredTest
를 상속받아 테스트 코드 작성에만 집중하면 되게끔 구성해두었다.
build.gradle 의존성
ext {
testcontainersVersion = '1.19.2'
}
dependencies {
//...
// test container
testFixturesImplementation "org.testcontainers:testcontainers:${testcontainersVersion}"
testFixturesImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}"
testFixturesImplementation 'org.testcontainers:junit-jupiter'
testFixturesImplementation 'org.testcontainers:postgresql'
// rest-assured
testFixturesImplementation 'io.rest-assured:rest-assured'
}
TestContainers.java
@Testcontainers
public abstract class TestContainers {
@Container
static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:13")
.withUsername("postgres")
.withPassword("1234")
.withDatabaseName("test")
.withInitScript("init.sql");
@DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.jpa.hibernate.ddl-auto", () -> "none");
}
}
RestAssuredTest.java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class RestAssuredTest extends TestContainers {
@LocalServerPort
private int port;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
// ...
}
❌ Redis 테스트 컨테이너 연동(실패 버전)
Redis 테스트 컨테이너 연동을 위해 TestContainers
클래스에 다음 설정을 추가했다.
@Testcontainers
public abstract class TestContainers {
private static final int REDIS_PORT = 6379;
// ...
@Container
static GenericContainer<?> redisContainer = new GenericContainer<>("redis:6.2-alpine")
.withExposedPorts(REDIS_PORT);
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", redisContainer::getHost);
registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(REDIS_PORT));
}
}
이후 테스트 코드를 작성하고 돌리는데... 정상적으로 동작하는 듯 했지만
왠걸,,, 개발 환경 Redis
에 테스트 데이터가 들어가 있었다.
분명 @DynamicPropertySource
를 통해 값을 오버라이딩을 했지만, 왜 이렇게 된 것일까...?
테스트 컨테이너 실행 과정과 연동 실패 원인
사실 테스트 컨테이너가 실행되는 과정을 정확히 알고 있지 못했던 것 같다.
트러블슈팅 과정에서 디버깅 포인트를 찍어 하나씩 따라간 결과 테스트 컨테이너는 다음과 같은 순서로 진행된다.
- 테스트 컨테이너가 실행되면 호스트 컴퓨터에서 랜덤 포트로 실행되는 테스트 컨테이너의 포트를
getMappedPort()
를 통해 6379 포트와 매핑된 랜덤 포트의 정보를 가져온다. - application.yml에 명시된 레디스 컨테이너의 host와 port를 오버라이딩한다.
- RedisConfig 설정에 적용한다.
그런데 내가 작성한 RedisConfig
에는 개발 환경에 적용할 Redis Cluster
정보밖에 없었다.
그래서 당연히 1번 2번 과정이 정상적으로 진행되었지만, RedisProperties
에는 host
와 port
정보가 없으니
3번 과정에서 1, 2번 과정이 무시가 되고 개발환경의 RedisConfig
가 테스트 컨테이너와 커넥션이 이루어졌던 것이었다.
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.nodes());
redisClusterConfiguration.setMaxRedirects(redisProperties.maxRedirects());
redisClusterConfiguration.setPassword(redisProperties.password());
return new LettuceConnectionFactory(redisClusterConfiguration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
// ...
}
}
⭕️ Redis 테스트 컨테이너 연동(성공 버전)
기존 TestContiners에 설정했던 것은 그대로 두고 테스트 환경에 적용할 Config
를 작성해주었다.
우선 기존 RedisConfig
클래스에 @Profile
을 추가해주고
RedisTemplate
은 환경에 상관없이 적용될 것이기에 별도 파일로 분리해주었다.
이후 테스트 코드를 실행하니 테스트 컨테이너와 커넥션이 정상적으로 이루어진 것으로 확인했다!
RedisConfig.java
@Profile("dev") // 추가
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// ...
}
}
RedisTemplateConfig.java
별도 파일로 분리
@Configuration
@RequiredArgsConstructor
public class RedisTemplateConfig {
private final RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
...//
}
}
RedisTestConfig.java 추가
RedisTestConfig
추가
@Profile("test")
@Configuration
public class RedisTestConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
}
TestContainers.java
마지막으로 테스트 컨테이너에 test profile
설정을 적용하기 위해 @ActiveProfile
을 추가해주었다.
@ActiveProfiles("test")
@Testcontainers
public abstract class TestContainers {
}
👋 마치며
스프링부트가 워낙 자동으로 해주는 것이 많다보니 제대로 알고 있지 않으면 항상 문제가 생긴다는 것을 다시 한번 느끼게 되었다.
'💻 프로그래밍 > Spring' 카테고리의 다른 글
[SpringBoot] WebMvcConfiguration의 동작 원리 (1) | 2024.09.08 |
---|---|
[Spring] MVC 요청 흐름 - Intercerptor가 먼저일까? DispatcherServlet이 먼저일까? (7) | 2024.08.27 |
[SpringBoot] WebMvcConfigurer CORS 설정이 적용 안되는 문제(feat. preflight 요청)(수정) (0) | 2024.08.17 |