Notice
Recent Posts
Recent Comments
관리 메뉴

즐겁게, 코드

프론트엔드 테스팅 두 번째 걸음 : 비동기 코드 테스팅 본문

🎨 프론트엔드/Testing

프론트엔드 테스팅 두 번째 걸음 : 비동기 코드 테스팅

Chamming2 2021. 8. 19. 16:10

비동기 코드 테스팅

지난 글에서 jest 로 기본적인 테스트를 구성하는 방법을 다루었다면, 조금 더 나아가 비동기적으로 동작하는 코드를 테스팅해 보겠습니다.

테스팅 설정

이전 글에서 테스트 코드는 node.js 환경에서 동작한다고 언급했는데요, 따라서 @babel/preset-env 프리셋을 통해 트랜스파일을 수행하면 ES6 문법을 거의 다 사용할 수 있지만, async-await 문법을 사용하려 하면 ReferenceError: regeneratorRuntime is not defined 라는 오류를 만나게 됩니다.

 

이를 해결하기 위해 @babel/plugin-transform-runtime 이라는 바벨 플러그인을 설치하고 적용해줍니다.

yarn add -D @babel/plugin-transform-runtime

루트 경로에 [babel.config.js] 를 다음과 같이 작성합니다.

  • 만약 @babel/preset-env 를 설치하지 않았다면, 똑같이 데브디펜던시로 설치해주시면 됩니다.
// babel.config.js

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["@babel/preset-env"], // ES6 문법 트랜스파일
    plugins: ["@babel/plugin-transform-runtime"], // async-await 문법 트랜스파일
  };
};

이제 비동기 코드를 테스트할 수 있게 되었습니다!

비동기 테스트 1. 콜백 함수

첫 번째 비동기 시나리오는 콜백을 사용하는 경우입니다.
비동기 테스트를 위해 1초 후 데이터를 반환하는 함수를 만들어 보고, 이를 테스트해 보겠습니다.

// getData.js
export const getData = (callback) => {
  setTimeout(() => {
    const data = {
      status: "success",
      data: "샘플 데이터",
    };
    callback(data);
    return data;
  }, 1000);
};
// getData.test.js
import { getData } from "../getData.js";

test("1초 후 데이터를 불러옵니다.", () => {
  // Arrange
  const targetData = { status: "success", data: "샘플 데이터" };
  // Assert (콜백)
  const callback = (data) => {
    expect(data).toEqual(targetData);
  };
  // Act
  getData(callback);
});

성공인가..?

이걸 보시고 '와! 테스트를 통과했어요!' 라고 하시면 절대 안됩니다!
한번 테스트 대상이 될 데이터를 이상하게 조작한 후 다시 테스트를 실행해 보겠습니다.

// getData.js
export const getData = (callback) => {
  setTimeout(() => {
    const data = {
      status: "success",
      data: "샘플 데이터",
    };
    // 실제 데이터 대신 빈 객체를 전달해 보겠습니다.
    callback({});
    return data;
  }, 1000);
};

올바른 데이터가 아닌 빈 객체를 전달받았음에도 테스트를 통과하는 모습입니다.

왜냐 하면, 테스트 코드는 함수 안에서 비동기적인 작업이 수행된다는 것을 인지하지 못하고 getData(callback) 의 호출이 끝난 직후에 평가를 진행하기 때문입니다.

 

따라서 데이터를 수신하는 1초 사이에 이미 모든 평가가 끝나버리고, 테스트를 통과하지 못한 케이스가 없으므로(수행할 테스트 로직을 찾지 못했으므로) 어떤 데이터를 불러오든 테스트는 성공하게 됩니다.

언제나 성공하는 테스트라면 수행하는 의미가 없겠죠?
따라서 비동기 로직이 끝날 때까지 기다리게 하기 위해 done 이라는 인자를 추가로 테스트 함수에 전달해야 합니다.

// getData.test.js
// test 함수의 콜백 인자로 done 이라는 함수를 전달합니다.
import { getData } from "../getData.js";

test("1초 후 데이터를 불러옵니다.", (done) => {
   // Arrange
  const targetData = { status: "success", data: "샘플 데이터" };
  const callback = (data) => {
    // Assert (콜백)
    try {
      expect(data).toEqual(targetData);
      // 이제 테스트 코드는 done이 호출될 때까지 기다립니다.
      done();
    } catch (err) {
      done(err);
    }
  };
  // Act
  getData(callback);
});

이전에는 테스트 코드 내부에 있는 함수를 동기적으로 실행하기만 하면 끝이었지만, 이제 done() 함수가 호출될 때까지 테스트를 기다리므로 비동기 작업을 검사할 수 있게 됩니다.

그리고 다시 빈 객체를 전달하면, 이번에는 테스트가 실패를 잡아내는데 성공한 모습입니다.

비동기 테스트 2. 프라미스와 async-await

두 번째 비동기 시나리오는 프라미스를 사용하는 경우입니다.
다행히도 프라미스를 사용한 비동기 동작은 async-await 구문을 통해 간단하게 테스트할 수 있습니다.

// getData.js

export const getData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(
      () =>
        resolve({
          status: "success",
          data: "샘플 데이터",
        }),
      1000
    );
  });
};
import { getData } from "../getData.js";

test("1초 후 데이터를 불러옵니다.", async () => {
  // Arrange
  const targetData = { status: "success", data: "샘플 데이터" };
  // Act
  const data = await getData();
  // Assert
  expect(data).toEqual(targetData);
});

하지만 테스팅을 할 때는 실제 API나 데이터베이스에 연결해 테스트를 진행하지 않습니다.
다음 번에는 API를 흉내내는 목업 함수로 테스팅을 진행하는 방법을 소개하겠습니다.

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆