선착순 이벤트 기능에서 Redis Lock을 사용하기 위해 RedisLockManager를 구현했다.
@Component
@RequiredArgsConstructor
public class RedisLockManager {
private final RedissonClient redissonClient;
}
하지만 이후 빌드를 실행하자 테스트 단계에서 실패했다.
bash ./gradlew clean build
처음 발생한 에러는 다음과 같았다.
Team8SaleCommerceApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException
RedisLockManager는 RedissonClient를 생성자 주입으로 받고 있다.
그런데 프로젝트에 아직 RedissonClient를 Spring Bean으로 등록하는 설정 클래스가 없었다.
즉, Spring이 애플리케이션 컨텍스트를 로딩하면서 다음 흐름으로 실패했다.
RedisLockManager Bean 생성 시도
↓
RedissonClient 필요
↓
Spring Context 안에 RedissonClient Bean 없음
↓
NoSuchBeanDefinitionException 발생
RedissonClient를 Bean으로 등록하기 위해 RedissonConfig를 추가했다.
package com.example.team8salecommerce.global.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Redisson 설정 클래스
*
* Redis Lock을 사용하기 위해 RedissonClient를 Spring Bean으로 등록한다.
*/
@Configuration
public class RedissonConfig {
@Value("${spring.data.redis.host:localhost}")
private String redisHost;
@Value("${spring.data.redis.port:6379}")
private int redisPort;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + redisPort);
return Redisson.create(config);
}
}
이제 RedissonClient Bean은 정상적으로 등록되었다.
하지만 다시 빌드하자 또 다른 에러가 발생했다.
Caused by: org.redisson.client.RedisConnectionException
Caused by: java.net.ConnectException
이번에는 Bean이 없어서 실패한 것이 아니라, 실제 Redis 서버 연결에 실패한 문제였다.
RedissonConfig가 추가되면서 테스트 실행 시에도 실제 Redis 서버에 연결하려고 했다.
하지만 로컬에서 Redis가 실행 중이 아니었기 때문에 다음 흐름으로 실패했다.
Spring Context 로딩
↓
RedissonClient Bean 생성
↓
localhost:6379 Redis 연결 시도
↓
Redis 서버 실행 중 아님
↓
RedisConnectionException 발생
즉, 문제의 핵심은 다음과 같았다.
실제 애플리케이션 실행에서는 Redis를 사용해야 한다.
하지만 contextLoads 테스트에서는 실제 Redis 서버 연결까지 필요하지 않다.
contextLoads() 테스트는 실제 Redis Lock 동작을 검증하는 테스트가 아니라, Spring 애플리케이션 컨텍스트가 정상적으로 로딩되는지만 확인하는 테스트이기 때문이다.
테스트 환경에서는 RedissonClient를 실제 Bean이 아니라 Mock Bean으로 대체했다.
package com.example.team8salecommerce;
import org.junit.jupiter.api.Test;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
/**
* 애플리케이션 컨텍스트 로딩 테스트
*
* RedisLockManager는 RedissonClient를 필요로 한다.
* 하지만 contextLoads 테스트에서 실제 Redis 서버까지 연결할 필요는 없으므로
* RedissonClient를 MockitoBean으로 대체한다.
*/
@SpringBootTest
class Team8SaleCommerceApplicationTests {
/**
* 테스트용 RedissonClient Mock Bean
*
* 실제 Redis 서버가 켜져 있지 않아도
* Spring Context가 정상적으로 로딩될 수 있게 한다.
*/
@MockitoBean
private RedissonClient redissonClient;
@Test
void contextLoads() {
}
}
수정 후 다시 빌드를 실행했다.
bash ./gradlew clean build
결과적으로 빌드가 성공했다.
BUILD SUCCESSFUL
아니다.
실제 프로젝트에서는 Redis를 사용한다.
다만 테스트 중 contextLoads()에서는 실제 Redis 서버에 연결할 필요가 없기 때문에 Mock Bean으로 대체한 것이다.
정리하면 다음과 같다.
| 상황 | Redis 사용 여부 |
|---|---|
| 실제 서버 실행 | 실제 Redis 사용 |
| 선착순 구매 Redis Lock | 실제 Redis 사용 |
| 결제 실패 재고 복구 Lock | 실제 Redis 사용 |
| 환불 재고 복구 Lock | 실제 Redis 사용 |
| contextLoads 테스트 | Mock RedissonClient 사용 |
실제 기능 테스트나 Postman 시연에서는 Redis 서버를 실행해야 한다.
docker run -d --name team8-sale-redis -p 6379:6379 redis:7-alpine
이미 Redis 컨테이너가 존재한다면 다음 명령어로 실행할 수 있다.
docker start team8-sale-redis
이번 문제는 두 단계로 나눠서 발생했다.
첫 번째 문제는 RedissonClient Bean이 등록되어 있지 않아 발생한 NoSuchBeanDefinitionException이었다.
이를 해결하기 위해 RedissonConfig를 추가하여 RedissonClient를 Spring Bean으로 등록했다.
두 번째 문제는 테스트 실행 시 실제 Redis 서버에 연결하려다가 실패한 RedisConnectionException이었다.
이를 해결하기 위해 contextLoads() 테스트에서는 RedissonClient를 Mock Bean으로 대체했다.
결과적으로 실제 애플리케이션에서는 Redis Lock을 정상적으로 사용할 수 있고, 기본 빌드 테스트는 Redis 서버 실행 여부에 의존하지 않도록 개선할 수 있었다.
외부 인프라를 사용하는 Bean을 추가할 때는 실제 실행 환경과 테스트 환경을 분리해서 생각해야 한다.
Redis, Kafka, S3, 외부 API처럼 별도 서버나 외부 서비스가 필요한 의존성은 테스트에서 실제 연결이 필요하지 않은 경우 Mock 처리하거나 테스트 전용 설정을 따로 두는 것이 좋다.
이번 경험을 통해 단순히 Bean을 등록하는 것뿐 아니라, 테스트 환경에서 해당 Bean이 어떻게 동작하는지도 함께 고려해야 한다는 점을 배웠다.