[SpringBoot] 테스트 코드를 위한 Embedded Redis

밍맹뭉·2025년 11월 11일

SpringBoot

목록 보기
5/6
post-thumbnail

2차 프로젝트 리팩토링 중 JWT RefreshToken 관리와 효율적인 랭킹 시스템 구현을 위해 Redis를 도입했다.
통합 테스트 코드를 작성하던 중, 로컬에서는 테스트가 잘 동작하지만 GitHub Actions CI 환경에서는 Redis에 접속할 수 없는 문제를 겪었다.
이번 글에서는 그 문제의 원인과, 이를 해결하기 위해 도입한 Embedded Redis 적용 과정 및 트러블슈팅을 정리해보려 한다.

🚨 Embedded Redis 도입 배경

RefreshToken과 실시간 랭킹을 위해 Redis 도입을 시작했다.
먼저 RefreshToken부터 Redis를 도입하고, 통합 테스트 코드를 작성했는데 로컬에서는 별 이상 없이 통과가 되는 것을 확인했다.
현재 진행 중인 프로젝트에서는 Github Actions을 활용한 CI 자동화 테스트를 사용 중인데, 아래처럼 Redis에 접속할 수 없다는 오류가 발생했다.

org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis

원인은 명확했다.

CI 환경은 우리가 사용하는 로컬 환경과 전혀 다른 독립된 서버에서 동작하기 때문에,
로컬에서 실행 중인 Docker Redis에 접근할 수 없었던 것이다.

이전에 환경 변수 누락으로 비슷한 문제를 겪은 적이 있었는데, 이번에도 테스트 환경이 제대로 분리되지 않아 같은 실수를 반복하게 되었다.

이 문제를 해결하기 위해, 테스트 환경에서 실제 Redis 서버를 띄우지 않고 내장 Redis(Embedded Redis)를 활용하는 방법을 찾게 되었다.


💡 Embedded Redis란?

Embedded Redis는 테스트 시 실제 Redis 서버 없이도 테스트가 가능하게 해주는 임베디드(내장형) Redis 서버이다.
테스트 시점에 Spring Context 내부에서 가볍게 실행되며,
운영 환경에서 사용하는 Redis와 완전히 격리되어 있기 때문에
데이터 손상이나 충돌 없이 독립적인 테스트가 가능하다.

💬 즉, 실제 Redis를 따로 띄우지 않아도 “테스트 전용 Redis 서버”를 메모리 상에서 구동하는 개념이다.


⚙️ Embedded Redis 적용 방법

1️⃣ 의존성 추가

Embedded Redis 라이브러리는 대표적으로 두 가지 버전이 존재한다.

https://mvnrepository.com/search?q=embedded+redis

첫번째로 ozimov에서 제공되는 라이브러리는 업데이트가 안된지 꽤나 오래된 것을 확인할 수 있다.
또한 해당 의존성의 경우에는 리눅스용 기준으로 실행되는 구조라 Mac 환경에서는 실행이 제대로 되지 않는 경우도 있다고 한다.
우리 팀에는 Mac을 사용하는 팀원이 있었기에 이 의존성은 사용하지 않기로 결정했다.

두번째는 github에서 제공되는 라이브러리는 비교적 최신까지 업데이트가 되었고, Mac 또한 호환이 잘 되기에 요즘 더 많이 쓰이는 라이브러리라고 한다. 따라서 해당 라이브러리를 사용하게 되었다.

build.gradle

testImplementation("com.github.codemonstur:embedded-redis:1.4.3")

2️⃣ Embedded Redis 설정 파일 작성 — TestRedisConfig

(참고로 아직 미완성 코드이므로 최종 완성 코드는 제일 하단에 있는 코드를 참고해주세요!)

파일 위치 - /test/com/back/config

@TestConfiguration
public class TestRedisConfig {

    private RedisServer redisServer;

    @Value("${spring.data.redis.port}")
    private int port;

	//테스트 시작 전 Embedded Redis 서버 시작
    //port와 host 설정
    @PostConstruct
    public void startRedis() throws IOException {

        redisServer = new RedisServer(port);
        redisServer.start();

        System.setProperty("spring.data.redis.port", String.valueOf(port));
        System.setProperty("spring.data.redis.host", "localhost");

    }

	//테스트 끝난 후 Embedded Redis 서버 종료
    @PreDestroy
    public void stopRedis() {
        if (redisServer != null) {
            redisServer.stop();
        }
    }
}

테스트 클래스에서는 다음과 같이 설정 파일을 import한다.

@Import(TestRedisConfig.class)

@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
@Transactional
@ActiveProfiles("test")
@Import(TestRedisConfig.class)
public class UserControllerTest{
}

3️⃣ application-test.yml 수정

(마찬가지로 아직 미완성 코드이므로 최종 완성 코드는 제일 하단에 있는 코드를 참고해주세요!)

spring:
  data:
    redis:
      host: localhost
      port: 6379

4️⃣ GitHub Actions 환경 변수 추가

jobs:
  build:
    ```

    env:
      SPRING_PROFILES_ACTIVE: test
      SPRING_DATA_REDIS_HOST: localhost
      SPRING_DATA_REDIS_PORT: 6379

CI 환경에서도 동일한 Redis 설정을 인식하도록 GitHub Actions workflow 파일에 Redis 관련 환경 변수를 추가한다.


여기까지 진행을 했을 때는 아직 Embedded Redis를 사용하는 테스트 클래스가 하나 밖에 없었기에 크게 문제가 없어보였다.
하지만 이후 Ranking에도 Redis를 도입하고 테스트 코드를 작성했을 때 포트 충돌이 일어나는 문제가 발생하였다.


⚠️ 포트 충돌 발생

Embedded Redis에서는 포트 충돌을 방지하기 위해 포트 번호를 랜덤으로 설정하여 실행하는 코드가 추가적으로 필요하다. 나는 그 부분을 빼고 진행했기에 모든 테스트가 6379 포트를 공유해서 문제가 발생했다.


✅ 최종 코드

따라서 수정한 코드는 아래와 같다.

1️⃣ TestRedisConfig 수정

@Configuration
@Profile("test")
public class TestRedisConfig {

    private RedisServer redisServer;
    private int redisPort;

	//테스트 시작 전 Embedded Redis 서버 시작
    //port와 host 설정
    @PostConstruct
    public void startRedis() throws IOException {
        redisPort = findAvailableTcpPort();
        redisServer = new RedisServer(redisPort);
        redisServer.start();

        // Spring Boot Redis 설정에 반영
        System.setProperty("spring.data.redis.host", "localhost");
        System.setProperty("spring.data.redis.port", String.valueOf(redisPort));

        System.out.printf("Embedded Redis 서버 시작 (port: %d)%n", redisPort);
    }

	//테스트 끝난 후 Embedded Redis 서버 종료
    @PreDestroy
    public void stopRedis() throws IOException {
        if (redisServer != null && redisServer.isActive()) {
            redisServer.stop();
            System.out.println("Embedded Redis 서버 종료");
        }
    }

	//포트 번호 랜덤 설정
    private int findAvailableTcpPort() throws IOException {
        try (ServerSocket socket = new ServerSocket(0)) {
            socket.setReuseAddress(true);
            return socket.getLocalPort();
        }
    }
}

2️⃣ application-test.yml 수정

spring:
  data:
    redis:
      host: localhost
      port: 0 #포트 랜덤 설정

🔍 변경된 핵심 포인트

두 파일에서 크게 바뀐 점은 두가지이다.

① 포트 랜덤 할당

기존에는 포트를 고정(6379)해서 사용했기 때문에
테스트를 동시에 실행할 경우 이미 사용 중인 포트로 인해 Redis 실행 실패가 발생했다.

이를 해결하기 위해 ServerSocket(0)을 활용해 사용 가능한 포트를 시스템이 자동으로 선택하도록 수정했다.
이렇게 하면 매 테스트마다 서로 다른 포트로 Embedded Redis를 실행할 수 있다

② 어노테이션 변경: @TestConfiguration → @Configuration + @Profile("test")

어노테이션을 바꾼 이유는 다음과 같다.

현재 application-test.yml의 포트번호를 어차피 랜덤으로 설정되기 때문에 기존에 6379로 작성해두었던 것을 0으로 변경하였다.

하지만 기존에 @TestConfiguration을 사용했는데, 이 경우 테스트 전용 Bean이 기존 설정 이후에 로드되는 특징이 있다.

즉,

1. RedisConfig(운영용 설정)가 먼저 로드됨

2. 이후 TestRedisConfig가 적용됨

3. 하지만 이미 spring.data.redis.port=0 상태에서 RedisConnectionFactory가 생성됨

⇒ 연결 실패 발생 ❌

이를 해결하기 위해 @Configuration + @Profile("test")으로 변경하여 Spring Context 초기화 시점에 TestRedisConfigRedisConfig과 함께 로드되도록 수정했고, 그 결과 문제가 해결된 것을 확인할 수 있었다.


🎯 결과

✅ 테스트 실행 시 포트가 랜덤으로 자동 할당되어 충돌 없음

✅ 실제 Redis 데이터가 삭제되던 문제 해결 (테스트용 Redis와 완전 분리)

✅ CI 환경에서도 안정적으로 Redis 기반 테스트 실행 가능


💬 회고

이번 이슈는 단순히 Redis 설정 문제를 넘어서, Spring Bean 로딩 순서와 테스트 환경 분리의 중요성을 깊게 이해할 수 있었던 경험이었다.

사실 중간중간 포트 중복으로 인해 실제 운영 Redis 데이터가 삭제되는 등 꽤 심각한 문제가 있었다.
처음에는 원인 파악보다는 “빨리 해결하고 싶다”는 마음으로 로그를 대충 훑었는데, 결국 문제를 더 복잡하게 만들었다는 걸 나중에 깨달았다.

“에러 로그를 정확히 분석하고, 원인을 단계별로 추적하는 습관이 얼마나 중요한지 다시 한 번 느꼈다.”

또한 @Configuration, @TestConfiguration, @Profile 같은 어노테이션의 동작 순서를 제대로 이해하지 못한 것도 초기에 문제 해결을 늦춘 요인이었다.

앞으로는 단순히 동작하게 만드는 것보다, 왜 이렇게 동작하는지를 구조적으로 이해하고 코드를 작성하는 개발자가 되어야겠다는 생각이 들었다.

profile
배우고 기록하며 성장하고 있습니다 👩‍💻

0개의 댓글