컨트롤러 통합 테스트를 진행하면서 로그인 후 레디스에 jwt 토큰을 저장해주는 로직을 검증하는 과정에서 NullPointerException이 발생했다.
@Test
@DisplayName("로그인 성공")
void loginSuccess() throws Exception {
// WHEN
ResultActions resultActions = mockMvc
.perform(post("/member/login")
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("username", "test")
.param("password", "1234")
)
.andDo(print());
// THEN
MvcResult mvcResult = resultActions
.andExpect(status().is3xxRedirection())
.andExpect(handler().handlerType(MemberController.class))
.andExpect(handler().methodName("login"))
.andReturn();
// 쿠키 검증
MockHttpServletResponse response = mvcResult.getResponse();
Cookie[] cookies = response.getCookies();
assertThat(cookies).isNotNull();
assertThat(cookies).hasSize(2);
// 액세스 토큰 검증
Cookie accessToken = getCookie(cookies, "accessToken");
assertThat(accessToken).isNotNull();
// 리프레시 토큰 검증
Cookie refreshToken = getCookie(cookies, "refreshToken");
assertThat(refreshToken).isNotNull();
// 레디스 저장 확인 및 토큰 값 비교
Member member = memberService.findByUsername("test").orElse(null);
String value = redisUt.getValue(String.valueOf(member.getId()));
assertThat(value).isEqualTo(refreshToken.getValue());
}
위 코드 redis.getValue() 부분에서 오류가 발생했다.
// 레디스 저장 확인 및 토큰 값 비교
Member member = memberService.findByUsername("test").orElse(null);
String value = redisUt.getValue(String.valueOf(member.getId()));
assertThat(value).isEqualTo(refreshToken.getValue());
레디스에 키가 어떻게 저장되는지 확인한 결과, 아래와 같이 이스케이프 시퀀스로 값이 저장되는 걸 확인할 수 있었다.
이렇게 저장되니 getValue(Long id)로 찾으려 해도 찾을 수가 없었던 것!

문제의 원인은 크게 2가지 였다.
현재 프로젝트에서 레디스의 키 값으로 String타입의 email과 Long 타입의 id가 사용되기 때문에, id값을 넣을 때마다 형변환 해주는 것도 지저분해 보여서
다양한 자료형의 키 값을 받아보고자 setValue(), getValue() 메서드의 파라미터로 제네릭 타입 T를 사용한 것
Redis의 기본 Serializer가 JdkSerializationRedisSerializer이기 때문에 이스케이프 시퀀스 형태로 저장되는 것
public <T> String getValue(T key) {
ValueOperations<T, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
public <T> void setValue(T key, String value, long timeout) {
ValueOperations<T, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, value, timeout, TimeUnit.MILLISECONDS);
}
기존의 RedisUt.java 값을 아래와 같이 바꿔주고,
public String getValue(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
public void setValue(String key, String value, long timeout) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, value, timeout, TimeUnit.MILLISECONDS);
}
RedisConfig에서 Key serializer를 String으로 바꿔줌으로서 해결했다.
@Configuration
public class RedisRepositoryConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
// lettuce
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}

이제 정상적으로 값이 들어온다!