테스트 코드 작성을 통한 버그 수정
- 단위 테스트를 진행하면서 발견한 문제 #3
- 비동기 함수에 누락된
await
키워드
- 비동기 함수에 누락된
const roomExists = this.drawingService.existsRoom(roomId);
if (!roomExists) throw new RoomNotFoundException('Room not found');
const playerExists = this.drawingService.existsPlayer(roomId, playerId);
if (!playerExists) throw new PlayerNotFoundException('Player not found in room');
drawingService의 existsRoom 메서드와 existsPlayer 메서드는 비동기 함수이지만, await
키워드가 누락되어 있었습니다. 이 때문에 boolean
타입이 아닌 Promise<boolean>
타입으로 반환되어 바로 아래의 if 조건문에서 에러 체크가 되지 않는 문제가 있었습니다.
테스트 코드 작성을 통해 문제가 되는 부분을 정확히 파악할 수 있었고, 해당 부분에 await
키워드를 추가함으로써 정상적으로 동작하게 되었습니다.
- 통합 테스트, e2e 테스트를 진행하면서 발견한 문제 #9
- handleConnection 메서드 내 에러 처리 로직이 의도대로 동작하지 않는 문제
@WebSocketGateway({
cors: '*',
namespace: '/socket.io/drawing',
})
@UseFilters(WsExceptionFilter)
export class DrawingGateway implements OnGatewayConnection {
@WebSocketServer()
server: Server;
constructor(private readonly drawingService: DrawingService) {}
handleConnection(client: Socket) {
const roomId = client.handshake.auth.roomId;
const playerId = client.handshake.auth.playerId;
if (!roomId || !playerId) throw new BadRequestException('Room ID and Player ID are required');
const roomExists = await this.drawingService.existsRoom(roomId);
if (!roomExists) throw new RoomNotFoundException('Room not found');
const playerExists = await this.drawingService.existsPlayer(roomId, playerId);
if (!playerExists) throw new PlayerNotFoundException('Player not found in room');
client.data.roomId = roomId;
client.data.playerId = playerId;
client.join(roomId);
}
// ...
}
기존 handleConnection 메서드는 @UseFilters(WsExceptionFilter)
필터를 사용해 에러 처리를 하려는 의도로 작성된 것으로 보입니다.
그러나 테스트 결과, handleConnection 메서드에는 filters가 적용되지 않는다는 점을 확인할 수 있었습니다. 해당 내용은, NestJS repository의 Issue #336에서도 확인이 가능합니다. 해당 이슈에는 Filters가 @SubscribeMessage()
데코레이터가 적용된 메서드에만 동작한다는 내용이 명시되어 있습니다.
따라서, 기존 코드는 개발자의 의도와 다르게 동작하고 있었으며, 이를 해결하기 위해 Filters 동작을 직접 수행할 수 있도록 수정했습니다.
@WebSocketGateway({
cors: '*',
namespace: '/socket.io/drawing',
})
@UseFilters(WsExceptionFilter)
export class DrawingGateway implements OnGatewayConnection {
@WebSocketServer()
server: Server;
constructor(private readonly drawingService: DrawingService) {}
async handleConnection(client: Socket) {
const roomId = client.handshake.auth.roomId;
const playerId = client.handshake.auth.playerId;
if (!roomId || !playerId) {
client.emit('error', {
code: 4000,
message: 'Room ID and Player ID are required',
});
client.disconnect();
return;
}
// ...
client.data.roomId = roomId;
client.data.playerId = playerId;
client.join(roomId);
}
// ...
}
Filters에 담겨있는 것과 동일하게 error 이름의 이벤트를 발행하고, 에러 메시지를 수신받을 수 있게 변경하였습니다.
- 그림이 그려지지 않는 문제 #18
-
Repository에 포함된 오타 수정
2-a 문제를 해결하게 되고 난 이후 실제 서버 배포를 해본 결과, 한 가지 오류가 추가로 발생했습니다.
-
위 이미지와 같이 게임이 시작되면 플레이어를 찾을 수 없습니다.
오류로 인해 그림이 그려지지 않는 문제가 발생했습니다.
이 오류는 기존 Gateway 코드에 await 누락 및 에러 처리 로직 문제(1번 및 2번 상황)로 인해 에러 처리가 제대로 되지 않으면서 발견되지 않아, 이전에는 발견되지 않았던 문제가 정확히 드러나게 된 경우입니다.
문제를 해결하기 위해 Redis에 실제로 저장된 값과 코드를 비교하며 오류의 원인을 찾기 시작했습니다. 문제는 drawingRepository 코드에서 발생한 것으로 확인되었습니다.
Redis에는 다음 이미지와 같이 room:{roomId}:players:{playerId}
형태로 저장되고 있는데, 실제 코드에서는 키 이름에 s
가 빠진 room:{roomId}:player:{playerId}
형태로 존재 여부를 검사하고 있었습니다.
import { Injectable } from '@nestjs/common';
import { RedisService } from 'src/redis/redis.service';
@Injectable()
export class DrawingRepository {
constructor(private readonly redisService: RedisService) {}
// ...
async existsPlayer(roomId: string, playerId: string) {
const exists = await this.redisService.exists(`room:${roomId}:player:${playerId}`);
return exists === 1;
}
}
위 문제 때문에 Redis 내 해당 플레이어가 존재함에도 불구하고 무조건 false를 반환하게 되어 에러가 발생했습니다.
이 문제는 간단하게 s
를 추가하는 것으로 해결할 수 있었습니다.
chat.gateway.ts
chat.repository.ts
파일에도 위와 같은 문제가 발견되어 모두 수정했습니다.