일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MySQL
- nestjs
- dfs
- game
- 공룡게임
- cookie
- 게임
- Nest.js
- Dinosaur
- react
- Express
- MongoDB
- flask
- mongoose
- Bull
- OCR
- jest
- AWS
- Queue
- TypeScript
- GIT
- Python
- typeORM
- 정렬
- 자료구조
- nodejs
- class
- Sequelize
- JavaScript
- Today
- Total
포시코딩
1월29일 - Test Code 작성하면서 궁금한거 정리 본문
개요
테스트 코드를 작성하면서 의문이 들었던 부분이나 헤맸던 부분을 정리해봄
return이 avoid type인 메소드
users.repo
createUser = async (userInfo) => {
await this.usersModel.create({
email: userInfo.email,
password: userInfo.password,
// ...생략
});
};
unit test code
test('users.repository createUser Method success', async () => {
mockUsersModel.create = jest.fn(() => {
return 'test';
});
const userInfo = {
email: 'test@gmail.com',
password: 'password',
// ...생략
};
await usersRepository.createUser(userInfo);
expect(mockUsersModel.create).toHaveBeenCalledTimes(1);
expect(mockUsersModel.create).toHaveBeenCalledWith({
email: userInfo.email,
password: userInfo.password,
// ...생략
});
});
createUser 메소드를 실행했을 때의 결과값이 존재하지 않아
해당 메소드의 결과를 비교할 수단이 없다.
다른 리턴값이 있는 메소드의 경우
const result = await usersRepository.findOneByEmail('test@gmail.com');
expect(result).toEqual('test');
이런식으로 비교 테스트를 넣을 수 있지만
위 createUser는 불가능한 상태.. 이럴 때 다른 방법이 있을지 내가 한 방법이 맞는지 궁금
sequelize transaction 사용하는 경우
users.repo
decreasePoint = async (transaction, id, transferPoint) => {
const userInfo = await this.usersModel.findOne(
{ attributes: ['id', 'point'], where: { id }, },
{ transaction }
);
// ...생략
await userInfo.save({ transaction });
};
위와 같은 repository의 메소드가 있을 경우
transaction을 어떻게 넘겨 처리해야 좋을지 궁금했는데
test('users.repository increasePoint Method success', async () => {
mockUsersModel.findOne = jest.fn(() => {
return { id: 1, point: 10000, save: () => {} };
});
await usersRepository.increasePoint('transaction', 1, 10000);
expect(mockUsersModel.findOne).toHaveBeenCalledTimes(1);
expect(mockUsersModel.findOne).toHaveBeenCalledWith(
{ attributes: ['id', 'point'], where: { id: 1 }, },
{ transaction: 'transaction' }
);
});
일단 그냥 string으로 넣어 전달하는식으로 테스트를 했는데 일단 원하는 결과를 얻을 수 있었다.
이렇게 하는게 맞는지..?
덮어씌워져 버리는 parameter
service unit test code를 작성하면서
계속 반복되어 사용되는 파라미터에 대해 describe() 함수 들어가기 전에 세팅해놓고 진행을 했었다.
const userInfo = {
id: 1,
email: 'test@gmail.com',
password: 'password',
name: 'name',
phone: '01012345678',
address: 'address',
isAdmin: false,
};
근데 service의 회원가입 메소드에서
userInfo.password = await this.encryptPassword(userInfo.password);
암호화된 값을 다시 도로 저장하는 코드가 있어서 그런지
createUser(회원가입) 메소드를 테스트한 코드 뒤로
위의 공용으로 사용하는 userInfo 객체의 password가 암호화된 값으로 바꿔어져 있는 현상을 발견했다.
이 현상 때문에 뒤에 오는 모든 테스트 코드들이 영향을 받아 정상적인 테스트를 하지 못하게 되었었는데
userInfo 객체를 깊은 복사를 통해 하나 더 만들어서
unit test code
const mockUserInfo = {
id: 1,
email: 'test@gmail.com',
password: 'password',
name: 'name',
phone: '01012345678',
address: 'address',
isAdmin: false,
};
// ...생략
test('users.service createUser Method Fail - already registered', async () => {
const userInfo = { ...mockUserInfo };
이런식으로 복사본을 하나 만들어 사용하는 방법을 사용했고 문제를 해결할 수 있었다.
login 메소드의 redis 사용
users.service
const redisClient = require('../utils/redis.util');
// ...생략
login = async (email, password) => {
try {
// ...생략
const accessToken = await TokenManager.createAccessToken(user.id);
const refreshToken = await TokenManager.createRefreshToken();
await redisClient.set(refreshToken, user.id);
const TTL = parseInt(process.env.REDIS_REFRESH_TTL);
await redisClient.expire(refreshToken, TTL);
// ...생략
로그인 하는 과정에서 refresh token을 redis 캐시 메모리에 저장하는 코드가 있는데
테스트 코드를 일반적으로 작성하면 redis에 실제로 refresh token을 저장해버리는 상황이 발생했다.
이 부분에서 어떻게 하면 좋을지 고민을 많이 했는데
테스트 코드 작성중 해당 클래스의 내부 함수에 대해
jest.fn() 하면 그 코드로 대체되버리는 문제를 겪었다가 해결한 뒤로 알게된 사실을 바탕으로
redisClient는 밖에서 모듈화한 객체를 불러온 것이기 때문에
해당 redisClient 함수들을 jest.fn() 으로 덮어씌워도 괜찮은 상황이 될 것 같아 진행해보았다.
unit test code
const redisClient = require('../../../src/utils/redis.util');
// ...생략
test(`users.service login Method success`, async () => {
const userInfo = { ...mockUserInfo };
// ...생략
redisClient.set = jest.fn(() => {});
redisClient.expire = jest.fn(() => {});
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 200, accessToken, refreshToken, message: '로그인 되었습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
redisClient.set = jest.fn(() => {});
redisClient.expire = jest.fn(() => {});
딱 이 부분만 보면 되는데
실제로 이렇게 세팅해놓으니 그 다음줄에서
const response = await usersService.login(userInfo.email, userInfo.password);
실행되는 위 코드를 통해 redis에 refresh token이 저장되던 현상을 해결할 수 있었다.
또한 외부에서 불러오는 함수였기 때문에 다른 테스트 코드에도 영향을 미치지 않는 것을 확인할 수 있었다.
catch를 통해 리턴될 수 있는 여러 상황 모두 테스트 코드에?
users.service login 메소드
login = async (email, password) => {
try {
const user = await this.findOneByEmail(email);
if (!user) {
return { code: 400, message: '이메일 또는 비밀번호를 확인해주세요.' };
}
if (!(await this.checkPassword(password, user.password))) {
return { code: 400, message: '이메일 또는 비밀번호를 확인해주세요.' };
}
const accessToken = await TokenManager.createAccessToken(user.id);
const refreshToken = await TokenManager.createRefreshToken();
await redisClient.set(refreshToken, user.id);
const TTL = parseInt(process.env.REDIS_REFRESH_TTL);
await redisClient.expire(refreshToken, TTL);
return { code: 200, accessToken, refreshToken, message: '로그인 되었습니다.' };
} catch (err) {
console.log(err.message);
return { code: 500, message: '로그인에 실패하였습니다.' };
}
};
보면
- findOneByEmail()
- checkPassword() // 얘는 내부 함수라 제외
- TokenManager.createAccessToken()
- TokenManager.createRefreshToken()
- redisClient.set()
- redisClient.expire()
이 함수들을 통해 login 절차가 진행되고 있는데 여기중 하나에서 error가 생기면
밑의 catch문을 통해
return { code: 500, message: '로그인에 실패하였습니다.' };
이 코드가 실행된다.
테스트 코드에 이 각각의 함수가 error를 내는 상황을 모두 구현했는데
이게 맞다고 생각하면서도 조금 의문이라 한번 남겨 보았다.
test(`users.service login Method fail - error (1)`, async () => {
const userInfo = { ...mockUserInfo };
const afterPassword = await usersService.encryptPassword(userInfo.password);
const findOneByEmailReturnValue = { id: 1, password: afterPassword };
mockUsersRepository.findOneByEmail = jest.fn(() => {
return findOneByEmailReturnValue;
});
TokenManager.createAccessToken = jest.fn(() => {
return 'accessToken';
});
TokenManager.createRefreshToken = jest.fn(() => {
return 'refreshToken';
});
redisClient.set = jest.fn(() => {});
redisClient.expire = jest.fn(() => { throw new Error(); });
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 500, message: '로그인에 실패하였습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
test(`users.service login Method fail - error (2)`, async () => {
const userInfo = { ...mockUserInfo };
const afterPassword = await usersService.encryptPassword(userInfo.password);
const findOneByEmailReturnValue = { id: 1, password: afterPassword };
mockUsersRepository.findOneByEmail = jest.fn(() => {
return findOneByEmailReturnValue;
});
TokenManager.createAccessToken = jest.fn(() => {
return 'accessToken';
});
TokenManager.createRefreshToken = jest.fn(() => {
return 'refreshToken';
});
redisClient.set = jest.fn(() => { throw new Error(); });
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 500, message: '로그인에 실패하였습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
test(`users.service login Method fail - error (3)`, async () => {
const userInfo = { ...mockUserInfo };
const afterPassword = await usersService.encryptPassword(userInfo.password);
const findOneByEmailReturnValue = { id: 1, password: afterPassword };
mockUsersRepository.findOneByEmail = jest.fn(() => {
return findOneByEmailReturnValue;
});
TokenManager.createAccessToken = jest.fn(() => {
return 'accessToken';
});
TokenManager.createRefreshToken = jest.fn(() => {
throw new Error();
});
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 500, message: '로그인에 실패하였습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
test(`users.service login Method fail - error (4)`, async () => {
const userInfo = { ...mockUserInfo };
const afterPassword = await usersService.encryptPassword(userInfo.password);
const findOneByEmailReturnValue = { id: 1, password: afterPassword };
mockUsersRepository.findOneByEmail = jest.fn(() => {
return findOneByEmailReturnValue;
});
TokenManager.createAccessToken = jest.fn(() => {
throw new Error();
});
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 500, message: '로그인에 실패하였습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
test(`users.service login Method fail - error (5)`, async () => {
const userInfo = { ...mockUserInfo };
mockUsersRepository.findOneByEmail = jest.fn(() => {
throw new Error();
});
const response = await usersService.login(userInfo.email, userInfo.password);
expect(response).toEqual({ code: 500, message: '로그인에 실패하였습니다.' });
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
이렇게 각각 다 new Error를 내봄..
이미 테스트 코드 작성이 된 함수를 실행할 때마다 같은 테스트 코드 작성?
users.service.js
findOneByEmail = async (email) => {
return await this.usersRepository.findOneByEmail(email);
};
unit test code
test('users.service findOneByEmail Method success', async () => {
const userInfo = { ...mockUserInfo };
const findOneByEmailReturnValue = 'test';
mockUsersRepository.findOneByEmail = jest.fn(() => {
return findOneByEmailReturnValue;
});
const result = await usersService.findOneByEmail(userInfo.email);
expect(result).toEqual(findOneByEmailReturnValue);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
});
findOneByEmail() 함수에 대해 이미 체크를 하고 있는데
service의 다른 메소드에서 findOneByEmail()을 사용할 때 마다
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledTimes(1);
expect(mockUsersRepository.findOneByEmail).toHaveBeenCalledWith(userInfo.email);
계속 해줘야 하는게 맞나? .. 일단 해주긴 했음
'TIL' 카테고리의 다른 글
1월31일 - TypeScript: Utility Type (1) | 2023.01.31 |
---|---|
1월30일 - 통합 테스트(Integration Test) - 작성중 (0) | 2023.01.30 |
1월28일 - cookie와 path 설정 (0) | 2023.01.29 |
1월27일 - 궁금증 해결 (0) | 2023.01.27 |
1월26일 - console.log 자동완성, 객체 구조 분해 할당 재작명 (3) | 2023.01.26 |