-
WebSocket, Redis 의 테스트 코드를 작성하며 마주친 에러와 해결개발 2024. 8. 6. 21:39
WebSocketStompClient로 소켓 통신을 하며 Redis에 접근하는 테스트 코드를 작성하며 발생한 에러를 해결하는 과정을 담고 있습니다.
WebSocket은 실제로 동작하는 것을 꼼꼼히 확인해야 프론트와 연동하는 과정에서 어려움이 적을 거 같아서 통합테스트를 진행하면서 예기치 못한 상황들을 마주하였습니다.
WebSocket에 대한 테스트 코드를 작성하고자 하는 분들에게 조금이나마 참고가 되었으면 합니다.
문제 상황
- WebSocketStompClient 로 소켓 통신에 대한 테스트 코드를 작성
- 테스트 케이스는 4개로 아래와 같습니다.
- JWT 토큰 검증이 성공하면 소켓을 연결
- JWT 토큰 검증이 실패하면 에러 발생
- 소켓이 연결되면 Redis에 사용자를 저장
- 소켓이 해제되면 Redis에서 사용자를 제거
- 테스트 케이스를 작성하고 테스트를 실행하면 모든 테스트는 성공을 하지만 테스트가 종료되지 않는 상황이 발생했습니다.
테스트는 성공하지만 종료되지 않는다. - 테스트는 모두 성공했지만 테스트는 정상적으로 종료되지 못하고, 아래와 같은 로그를 남긴 뒤에야 종료가 되었습니다.
원인
- 위의 문제의 부분에서 redisTemplate 으로 소켓 연결 해제시 사용자의 정보를 제거하고 있는데 여기서 RedisCommandTimeoutException이 발생하고 있었습니다.
- 그래서 하나씩 로그를 따라가며 확인한 결과, 소켓을 해제하는 시점에 RedisServer가 이미 종료된 뒤라서 발생하는 문제였습니다.
- 왜 소켓을 해제하기 전에 종료가 되었는지를 파악하기 위해 다시 테스트 코드를 확인했습니다.
@DisplayName("소켓 연결 시 JWT 토큰이 유효하다면 정상적으로 연결된다") @Test void connectWebSocketWithValidJwtToken_success() throws Exception { // given StompHeaders headers = new StompHeaders(); headers.add("Authorization", "Bearer " + jwt); URI uri = new URI(String.format("ws://localhost:%d/ws", port)); // expected assertDoesNotThrow(() -> { StompSession stompSession = this.stompClient.connectAsync(uri, null, headers, new StompSessionHandlerAdapter() { }).get(5, TimeUnit.SECONDS); }); }
- JWT 토큰이 정상적이라면 소켓을 연결하는 과정에서 에러가 발생하지 않는 것을 검증하였습니다.
- 다른 테스트 케이스에서는 StompClient를 새로 할당하여 소켓에 연결하도록 테스트를 작성해두었는데, 이 부분에서 처음에 연결하였던 위의 코드는 StompClient를 새로 할당하였다해서 연결이 끊기지 않고 그대로 남아있는 것이었습니다.
- 다른 테스트 케이스에서는 소켓에 연결이 되지않거나, 연결을 하고 해제를 하면서 Redis에 데이터가 반영이 되었는지 확인을 해야했기에 명시적으로 아래와 같은 코드를 작성해주었기에 문제가 되지 않았습니다.
stompSession.disconnect();
- 문제가 되는 이유는 다음과 같습니다.
- 테스트를 실행하면 스프링 컨테이너가 올라오고 빈들이 등록된다.
- 테스트케이스들을 실행한다.
- 현재 테스트 환경에서는 테스트용 redis 환경을 갖추어두었고, 테스트가 끝나면 redis 서버가 종료되는 환경입니다.
- 스프링부트에 등록된 모든 빈들은 destroy 하는 과정에 웹소켓 관련된 빈들도 destroy 하게 됩니다.
- 웹소켓은 해제되지 않은 세션이 있기에 Redis에 접근하여 사용자의 정보 제거를 시도한다.
- 하지만 Redis 서버는 이미 종료된 뒤라 RedisCommandTimeoutException 이 발생하는 것이었습니다.
StompSubProtocolHandler 의 afterSessionEnded - 스프링부트가 종료되면서 StompSubProtocolHandler의 afterSessionEnded가 호출되고 세션에 있는 연결들을 끊어줍니다.
- 의존성을 따로 받지 않고 Event 를 발행하여 세션을 끊는 것을 확인할 수 있었습니다.
DefaultSimpUserRegistry의 onApplicationEvent - 디버그로 확인한 결과 DefaultSimpUserRegistry에서 SessionDisconnectEvent를 처리하여 연결된 세션 정보를 제거하는 것을 확인할 수 있었습니다.
해결
- 소켓 연결을 해제해야 하는 곳에 명시적으로 disconnect 하는 코드를 추가함으로써 테스트는 정상적으로 성공하고 종료할 수 있게 되었습니다.
생각해보면 간단히 해결할 수 있는 문제였지만 빨리 해결하지 못한 건 WebSocket에 대한 테스트 코드에 익숙하지 않았던 게 가장 큰 원인이라고 생각합니다.
'개발' 카테고리의 다른 글
JOOQ 테스트 환경 분리하기 (1) 2024.09.25 메인 기능, 부가 기능 분리 그리고 Transactional outbox pattern (2) 2024.09.04 테스트하기 안 좋은 코드 리팩토링하기 (0) 2024.08.20 부하테스트를 위한 더미데이터 준비 (0) 2024.08.08 Stomp, Redis 로 현재 접속중인 사용자 조회하기 (0) 2024.08.05