보통 어디서 어떻게 실패하였는지 테스트 코드를 작성하면서 동작시켜보면 알아 볼 수 있는데. 정상적으로 작성한 것 같은데도 에러를 뿜어냈다.
//로그인
async validateUser(
userInfo: LoginDto,
): Promise<{ accessToken; refreshToken } | undefined> {
//해당 email과 일치하는 데이터를 조회
const existUser: User = await this.userService.findByFields({
where: { email: userInfo.email },
});
//email과 같은 데이터가 없을 시 에러
if (!existUser) throw new NotFoundException('email을 확인 해주세요');
//비밀번호 검증
const validatePassword = await bcrypt.compare(
userInfo.password,
existUser.password,
);
//비밀번호 불일치 시 에러
if (!validatePassword)
throw new BadRequestException('password를 확인해 주세요');
//토큰에 넣어줄 정보 선언
const payload: Payload = { id: existUser.id, nickname: existUser.nickname };
// accessToken 발급
const accessToken = await this.createAccessToken(payload);
// refreshToken 발급
const refreshToken = await this.createRefreshToken(payload);
return { accessToken, refreshToken };
}
위 로그인 코드에 대한 test 케이스를 작성하기 위해 아래와 같이 테스트 코드를 작성하였다.
it('로그인 성공 케이스', async () => {
const user = {
id: 1,
email: 'test@test.com',
password: await bcrypt.hash('testpass', 10),
nickname: 'testnick',
};
const loginDto: LoginDto = { email: 'test@test.com', password: 'testpass' };
mockUserService.findByFields = jest.fn(() => user);
const result = await authService.validateUser(loginDto);
expect(result).toEqual({
accessToken: 'accessToken',
refreshToken: 'refreshToken',
});
});
어짜피 authService.validateUser()
를 테스팅 하는 것이고, 해당 결과는 result에 담긴다. 그리고 그 result는 위의 코드로직에서 반환하는 return { accessToken, refreshToken }
가 되어야한다.
하지만, 테스트를 실행해 보면 성공할 것이라 생각했던 것과 달리 실패가 뜨고 expect(result).toEqual({ accessToken: 'accessToken', refreshToken: 'refreshToken' })
에서 result가 { accessToken: 'refreshToken', refreshToken: 'refreshToken' }
를 반환하고 있다.
왜???
문제점은 각 토큰을 발급하는 함수가 실질적으로는 같은 jwtService.sign
함수를 사용하고 들어가는 secret 과 expiresIn을 다르게 함으로써 서로의 값이 달라지는데, 테스트 케이스에서는 해당 구현이 똑바로 되지 않아서 반환값의 일치를 보장하지 못했다.
// accessToken 발급
const accessToken = await this.createAccessToken(payload);
// refreshToken 발급
const refreshToken = await this.createRefreshToken(payload);
//----------------------------------------------------------------
//엑세스 토큰 생성
async createAccessToken(payload: Payload): Promise<string> {
const accessToken = await this.jwtService.sign(payload, {
secret: process.env.ACCESS_JWT_SECRET,
expiresIn: '5m',
});
return accessToken;
}
//리프레쉬 토큰 생성
async createRefreshToken(payload: Payload): Promise<string> {
const refreshToken = await this.jwtService.sign(payload, {
secret: process.env.REFRESH_JWT_SECRET,
expiresIn: '24h',
});
return refreshToken;
}
따라서 각 함수가 어떠한 값을 도출해 내줄 것인지 정해줄 필요가 생겼다.
it('로그인 성공 케이스', async () => {
const user = {
id: 1,
email: 'test@test.com',
password: await bcrypt.hash('testpass', 10),
nickname: 'testnick',
};
const loginDto: LoginDto = { email: 'test@test.com', password: 'testpass' };
mockUserService.findByFields = jest.fn(() => user);
jest
.spyOn(authService, 'createAccessToken')
.mockResolvedValue('accessToken');
jest
.spyOn(authService, 'createRefreshToken')
.mockResolvedValue('refreshToken');
const result = await authService.validateUser(loginDto);
expect(result).toEqual({
accessToken: 'accessToken',
refreshToken: 'refreshToken',
});
expect(mockUserService.findByFields).toBeCalledTimes(1);
expect(authService.createAccessToken).toBeCalledTimes(1);
expect(authService.createRefreshToken).toBeCalledTimes(1);
});
jest.spyOn()
을 이용해 각 함수를 감시?하고, 그 함수들이 어떤결과를 내놓을지 jest.mockResolvedValue()
을 통해 보장해 주었더니 정상적으로 동작하게 되었다.