본문으로 바로가기

[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가지 방식만 사용하는 것 많은 차이가 있다고 생각됩니다. 자신에게 맞는 방식을 사용하시면 될 것 같습니다. 도움이 되셨으면 좋겠습니다. 아래는 모든 테스트 코드 최종 결과입니다. 테스트 코드의 추가 장점 문서화가 잘 되는 것을 확인할 수 있습니다.

참고자료

마지막

해당 내용은 틀릴 수도 있습니다. 틀린 내용이 있으면 조언 부탁드립니다.

반응형