💬 들어가기 전에
현재 개발중인 프로젝트의 환경은 다음과 같다.
- 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' 카테고리의 다른 글
| [Spring] 서버 모니터링 - 1. GlobalExceptionHandler에서 에러 메시지를 Discord로 보내기 (0) | 2025.03.20 |
|---|---|
| [SpringBoot] WebMvcConfiguration의 동작 원리 (1) | 2024.09.08 |
| [Spring] MVC 요청 흐름 - Intercerptor가 먼저일까? DispatcherServlet이 먼저일까? (7) | 2024.08.27 |
| [SpringBoot] WebMvcConfigurer CORS 설정이 적용 안되는 문제(feat. preflight 요청)(수정) (0) | 2024.08.17 |