최근 코로나 확진자 수 정보를 받아오는 크롤러 코드를 작성하였다.
로컬 환경에서는 별 문제 없이 작동하고 구상했던대로 동작했기 때문에 문제없는 코드라고 판단하였다.
따라서 테스트 코드 작성하고 테스트를 실행 함에 있어서 문제 없겠구나 생각하였는데, 테스트는 실패하였다.
처음엔 테스트 실패의 이유를 도통 이해할 수 없어, 처음으로 StackOverflow에 질문까지 올리게 되었다. StackOverflow 질문 글
export const getCoronaData = async () => {
try {
// ... 크롤링 코드 생략
// fake 데이터 추가
const covidStatus = [
{
city: 'city',
totalCases: '100',
increasedCases: '100',
date: 'time',
},
];
const covidRepository = getRepository(Covid);
covidStatus.forEach(async (status) => {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
// expecting save() method at this part to be called, but it fails
return await covidRepository.save(
covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: status.time,
})
);
} else {
return await covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: status.time,
},
]);
}
});
} catch (err) {
console.error(err);
return;
}
};
코로나 확진자 수 정보를 크롤링 하여, covidStatus 배열에 담아, 순차적으로 DB에 저장 혹은 수정을 반영하는 코드이다.
describe('getCoronaData', () => {
it('should get a Covid Status data and return it', async () => {
typeorm.getRepository = jest.fn().mockReturnValue({
findOne: jest.fn().mockResolvedValue(null),
create: jest.fn(),
save: jest.fn(),
});
await getCoronaData();
expect(typeorm.getRepository).toHaveBeenCalledWith(Covid);
expect(typeorm.getRepository(Covid).findOne).toHaveBeenCalled();
expect(typeorm.getRepository(Covid).save).toHaveBeenCalled(); // this expect result is making an error
// Expected number of calls: >= 1
// Received number of calls: 0
});
});
테스트 실행 시, 테스트는 실패한다.
이유는 expect(typeorm.getRepository(Covid).save).toHaveBeenCalled();
부분이 호출 되지 않고 있다고 한다.
"음 ? 분명 실제로 잘 동작하던 코드인데 왜 안되지?"
코드를 뜯어 여러 시도를 해본 결과, 테스트 실패의 원인은 forEach()
부분이었다.
async
함수는 프로미스를 return 한다.forEach()
는 return 값이 존재 하지 않는다.forEach()
에서의 async
콜백은 await
가 보장되지 않는다고 한다.즉, forEach()
문 내의 로직들의 대한 처리가 return 됨을 기다려 주지 않고 다음 로직들에 대한 처리가 이루어진다.
이러한 이유로 Jest
가 save()
메서드에 대한 호출을 인지 할 수 없던 것이다.
문제의 원인은 파악하였고, 따라서 잘못 사용 하고 있던 forEach()
부분들에 대한 수정을 진행하였다.
Promise.all()
을 활용하는 방법 await Promise.all(
covidStatus.map(async (status) => {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
const newData = covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
});
return covidRepository.save(newData);
} else {
return covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
},
]);
}
})
);
for ... of
를 활용하는 방법 for (const status of covidStatus) {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
const newData = covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
});
return covidRepository.save(newData);
} else {
return covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
},
]);
}
}
이 두가지 방법도 차이점이 있는데, Promise.all()
은 내부 로직에 대한 처리가 병렬로 이루어져 순차적 동작하지 않는다.
반면 for ... of
문에서는 순차적으로 처리가 이루어 진다는 점이다.
참고
https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop