앞서 레디스 분산 락을 구현하기 위해 Redisson 레디스 환경을 설정했습니다.
이후 테스트를 위해 embedded-redis 라이브러리로 테스트 환경을 구성하려고 합니다.
먼저 build.gradle에 dependency를 추가해줍니다.
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.1'
그리고 test 폴더 밑에 Configuration을 생성합니다.
@Configuration
@Profile("test")
public class EmbeddedRedisConfig {
//...
}
이때 @Profile 어노테이션을 이용해 실제 환경에서 주입될 Configuration과 구분합니다.
(실제 환경에서 주입될 RedissonConfig에는 @Profile("!test")를 태그함)
test 환경의 EmbeddedRedisConfig에서 동일한 빈을 주입하더라도, 이는 실제 환경의 RedissonConfig 빈 주입 후 덮어씌우는 것이 됩니다.
따라서 실제 환경의 RedissonConfig가 실행되는 과정에서 에러가 발생할 수 있습니다.
(필자의 경우에는 redis.password 값이 test 환경의 application.yml에 존재하지 않아 에러가 남...)
실제 환경에서 redisson 라이브러리를 이용하기 위해 RedissonClient를 Bean으로 주입받아 사용하고 있습니다.
따라서 동일하게 test 환경에서도 RedissonClient를 빈으로 주입해줘야 합니다.
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port);
return Redisson.create(config);
}
이때 redisson이 연결할 redis 서버를 embedded-redis로 띄워줍니다.
import redis.embedded.RedisServer;
private RedisServer redisServer;
@PostConstruct
public void startRedis() throws IOException {
redisServer = RedisServer.builder().port(port).setting("maxmemory " + maxmemorySize + "M").build();
redisServer.start();
}
@PreDestroy
public void stopRedis() {
redisServer.stop();
}
이 때 @PreDestroy는 정상적으로 빈이 소멸되면서 실행되는 코드입니다.
그런데 test가 실패하는 경우 스프링이 정상적으로 종료되지 않으면서 PreDestroy가 호출되지 않습니다.
따라서, 테스트를 다시 실행하게 되면 port가 이미 점유되어 있어 아래와 같은 에러가 발생합니다.
java.lang.RuntimeException: Can't start redis server. Check logs for details.
해결 방법은 간단합니다.
redisServer.start()를 try-catch로 감싸주면 됩니다.
try {
redisServer.start();
log.info("레디스 서버 시작 성공");
} catch (Exception e) {
log.error("레디스 서버 시작 실패");
}
해결 방법은 https://rogal.tistory.com/entry/Embedded-Redis-를-쓰면서-겪은-문제와-해결방안 글을 참고했습니다.
최종적으로 작성된 embedded-redis configuration 파일은 다음과 같습니다.
package com.tiketeer.Tiketeer.configuration;
import java.io.IOException;
import org.junit.jupiter.api.DisplayName;
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;
import org.springframework.context.annotation.Profile;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import redis.embedded.RedisServer;
@Slf4j
@DisplayName("Embedded Redis 설정")
@Configuration
@Profile("test")
public class EmbeddedRedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.maxmemory}")
private int maxmemorySize;
private RedisServer redisServer;
@PostConstruct
public void startRedis() throws IOException {
redisServer = RedisServer.builder().port(port).setting("maxmemory " + maxmemorySize + "M").build();
try {
redisServer.start();
log.info("레디스 서버 시작 성공");
} catch (Exception e) {
log.error("레디스 서버 시작 실패");
}
}
@PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port);
return Redisson.create(config);
}
}
테스트 환경을 구성하며 @Configuration과 @TestConfiguration의 차이를 몰라 헤매기도 하고, 그 과정에서 @Profile 어노테이션을 활용해보기도 하는 등 우여곡절이 많았습니다.
@Profile 말고도 @ConditionalOnProperty 등의 어노테이션이 있으니 잘 알아보고 활용하면 좋을 것 같습니다.
@PostConstruct, @PreDestory도 잘 모르고 사용했다가 끝없는 Can't start redis server 굴레에 갇히기도 했었습니다...
따라서 위에서 언급한 어노테이션에 대해서는 추후 글을 따로 작성해보려고 합니다.