[Jest] 비동기 테스트 코드 작성
이번 게시글에서는 비동기 처리를 위한 테스트 코드 작성하는 방법을 알아보겠습니다. 비동기 처리하는 방법은 callback, promise, async await 이 있는데요. 이 3가지 모두 테스트 하는 방법을 알아보도록 하겠습니다.
비동기 처리를 위한 테스트 코드 작성하기
◆ callback 테스트 코드
callback 테스트 코드 먼저 확인해보겠습니다.
// cbLogin 코드
cbLogin: (cb, result) => {
setTimeout(() => {
if (!result) {
cb(() => {
throw new Error("network 에러");
});
} else {
cb(() => "로그인 성공");
}
}, 1000);
};
// cbLogin 테스트 코드
const { cbLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("callback 방식", () => {
it("1초 뒤 로그인 성공", () => {
function loginTest(result) {
expect(result()).toBe("로그인 성공");
}
cbLogin(loginTest, true);
});
it("1초 뒤 로그인 실패", () => {
function loginTest(result) {
expect(() => result()).toThrow("network 에러");
}
cbLogin(loginTest, false);
});
});
});
위 코드에 대해 간단하게 설명해보겠습니다.
- cbLogin 함수
cbLogin 함수는 첫번째 인자로 callback 함수, 두번째 인자로 result를 받습니다. 그리고 1초 뒤 result에 따라 callback 함수의 매개변수로 성공 or 실패(error)를 넘겨줍니다.
- cbLogin 테스트 코드
테스트 코드에는 성공 시 로그인 성공 결과, 실패시 에러 메시지를 예상한다고 작성하였습니다.
그렇다면 위 결과는 어떻게 될까요? 당연히 테스트가 성공하겠죠? 아래 결과를 확인해봅시다.
역시 우리가 생각한대로 테스트가 성공했습니다. 그런데 뭔가 이상한게 1초 뒤 로그인이 되어야 하는데 테스트 시간은 4ms 입니다. 왜 그럴까요? 그 이유는 위 테스트의 역할은 cbLogin 함수를 호출시키는 것 까지 입니다. 즉 일단 로그인 성공 유무와는 상관 없이 cbLogin을 호출 했으니 그걸로 성공한 것입니다. 이해가 되셨나요? 그렇다면 우리가 원하는대로 테스트를 하고 싶으면 어떻게 해야할까요? 아래 코드를 확인해봅시다.
const { cbLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("callback 방식", () => {
it("1초 뒤 로그인 성공", done => {
function loginTest(result) {
try {
expect(result()).toBe("로그인 성공");
done();
} catch (error) {
expect(() => result()).toThrow("network 에러");
done();
}
}
cbLogin(loginTest, true);
});
it("1초 뒤 로그인 실패", done => {
function loginTest(result) {
try {
expect(result()).toBe("로그인 성공");
done();
} catch (error) {
expect(() => result()).toThrow("network 에러");
done();
}
}
cbLogin(loginTest, false);
});
});
});
코드가 조금 많이 추가되었죠? 결국 여기서 중요한 것은 비동기 처리의 경우, 비동기 처리가 끝난 시점을 정확히 명시 해주어야 합니다. 그렇기 위해서 제공해주는 callback 함수인 done을 잘 사용해야 합니다. 2개의 테스트 모두 성공 / 실패 이후 done callback 함수를 실행 한 것을 확인할 수 있습니다. 이해가 되셨을까요? 그렇다면 이제 promise 방식에 대해 알아보겠습니다.
◆ promise 테스트 코드
promise는 어떻게 테스트 할 수 있을까요? 빠르게 확인해봅시다.
// promiseLogin 코드
promiseLogin: result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!result)
reject(() => {
throw new Error("network 에러");
});
else resolve("로그인 성공");
}, 1000);
});
};
// promiseLogin 테스트 코드
const { promiseLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("promise 방식", () => {
it("1초 뒤 로그인 성공", () => {
promiseLogin(true).then(result => {
expect(result).toBe("로그인 성공");
});
});
it("1초 뒤 로그인 실패", () => {
promiseLogin(false)
.then()
.catch(result => {
expect(() => result()).toThrow("network 에러");
});
});
});
});
위 코드처럼 구현하면 테스트에 성공합니다. 아래 결과를 확인해봅시다.
테스트에 통과했습니다. 그러나 이전 callback 함수 테스트한 것을 보았듯이 이번 테스트도 1초보다 더 빠르게 끝났습니다. 즉 이번에도 호출만 하고 끝이난 것이죠. 그럼 어떻게 하면 될까요? 동일하게 done을 사용하면 됩니다. 아래 코드처럼 수정해봅시다.
const { promiseLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("promise 방식", () => {
it("1초 뒤 로그인 성공", done => {
promiseLogin(true).then(result => {
expect(result).toBe("로그인 성공");
done();
});
});
it("1초 뒤 로그인 실패", done => {
promiseLogin(false)
.then()
.catch(result => {
expect(() => result()).toThrow("network 에러");
done();
});
});
});
});
이제 우리가 예상했던 것처럼 테스트 코드가 수행 ;되었습니다. 그런데 코드가 너무 가독성이 떨어지지 않나요? 따라서 가독성을 높이기 위해 done을 사용하지 않는 방법도 있습니다. 바로 return을 이용하면 됩니다. 아래 코드를 확인해봅시다.
const { promiseLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("promise 방식", () => {
it("1초 뒤 로그인 성공", () => {
return promiseLogin(true).then(result => {
expect(result).toBe("로그인 성공");
});
});
it("1초 뒤 로그인 실패", () => {
return promiseLogin(false)
.then()
.catch(result => {
expect(() => result()).toThrow("network 에러");
});
});
});
});
done을 사용했던 것과 동일하게 정상 동작하는 것을 확인할 수 있습니다. promise의 경우 다른 방법도 있습니다. 바로 resolves, rejects를 사용하면 됩니다.
const { promiseLogin } = require("../asynctest.js");
describe("비동기 처리 테스트", () => {
describe("promise 방식", () => {
it("1초 뒤 로그인 성공", () => {
return expect(promiseLogin(true)).resolves.toBe("로그인 성공");
});
it("1초 뒤 로그인 실패", () => {
return expect(promiseLogin(false)).rejects.toThrow("network 에러");
});
});
});
위 코드에서 확인할 수 있는 것처럼 resolves, rejects를 사용하면 보다 더 깔끔하게 promise로 비동기 테스트 처리를 할 수 있습니다. 그 다음 마지막으로 async await 방법으로 테스트를 해보겠습니다.
◆ async await 테스트 코드
async await 방식은 쉽습니다. 그냥 평소에 사용하던 async await 처럼 사용하시면 됩니다. 아래 코드를 참고해봅시다.
describe("async await 방식", () => {
it("async await - 1초 뒤 로그인 성공", async () => {
const result = await promiseLogin(true);
expect(result).toBe("로그인 성공");
});
it("async await - 1초 뒤 로그인 실패", async () => {
try {
return await promiseLogin(false);
} catch (result) {
expect(() => result()).toThrow("network 에러");
}
});
});
기존 promise 테스트 코드를 async await으로 변경한 것 밖에 없습니다. 추가로 async await 방식도 resolves, rejects를 사용 할 수 있습니다. 아래 코드를 확인해봅시다.
describe("async await 방식", () => {
it("async await & resolvers - 1초 뒤 로그인 성공", async () => {
return await expect(promiseLogin(true)).resolves.toBe("로그인 성공");
});
it("async await & rejects - 1초 뒤 로그인 실패", async () => {
return await expect(promiseLogin(false)).rejects.toThrow("network 에러");
});
});
코드가 간단하여 추가적인 설명은 하지 않겠습니다. 그리고 사실 위 코드는 더 줄여서 사용가능합니다. arrow function을 사용하였기 때문에 굳이 {}와 return을 안적어주어도 됩니다. 그럼 더 깔끔해지겠죠?
이번 게시글에서는 callback, promise, async await 을 이용하여 비동기 처리 테스트 방법을 알아보았습니다. 각각의 방식에서도 여러 방법을 알아보았는데, 많은 방식을 알고 1가지 방식을 사용하는 것과 1가지 방식만 알아서 1가지 방식만 사용하는 것 은 많은 차이가 있다고 생각됩니다. 자신에게 맞는 방식을 사용하시면 될 것 같습니다. 도움이 되셨으면 좋겠습니다. 아래는 모든 테스트 코드 최종 결과입니다. 테스트 코드의 추가 장점 문서화가 잘 되는 것을 확인할 수 있습니다.
참고자료
마지막
해당 내용은 틀릴 수도 있습니다. 틀린 내용이 있으면 조언 부탁드립니다.
'기타 (+ Legacy) > Legacy' 카테고리의 다른 글
[React] useCallback, useMemo 잘 쓰기 (0) | 2022.03.11 |
---|---|
[테스트 코드 - Jest] mock 함수 (0) | 2022.03.09 |
[테스트 코드 - Jest] SuperMarket 테스트 코드 작성 및 에러 처리 (0) | 2022.03.07 |
[테스트 코드 - Jest] Matchers & SuperMarket 테스트 코드 작성 준비 (0) | 2022.03.04 |
[테스트 코드 - Jest] Test Code 작성 및 Jest 설정 추가 (0) | 2022.03.03 |