른록노트

[Javascript] Jest - Testing Asynchronous Code 본문

Programming/[Javascript]

[Javascript] Jest - Testing Asynchronous Code

른록 2021. 12. 4. 19:49

Testing Asynchronous Code

JavaScript에서는 코드가 비동기적으로 실행되는 것이 일반적입니다. 비동기식으로 실행되는 코드가 있는 경우 Jest는 테스트 중인 코드가 완료된 시점을 알아야 다른 테스트로 넘어갈 수 있습니다. Jest는 이것을 처리하는 몇 가지 방법이 있습니다.

Callbacks

The most common asynchronous pattern is callbacks.
가장 일반적인 비동기 패턴은 콜백입니다.

예를 들어 일부 데이터를 가져오고 완료되면 콜백(data)을 호출하는 fetchData(callback) 함수가 있다고 가정해 보겠습니다. 이 반환된 데이터가 'hello' 문자열인지 테스트하려고 합니다.

기본적으로 Jest 테스트는 실행 끝에 도달하면 완료됩니다. 즉, 이 테스트가 의도한 대로 작동하지 않습니다.

function fetchData(cb) {
  var result;
  axios.get('http://localhost:3000').then((response) => {
    result = cb(response.data); //response.data는 hello
  });
  return result;
}

// Don't do this!
test('the data is hello', () => {
  function callback(data) {
    expect(data).toBe('hello');
  }

  fetchData(callback);
});

문제는 콜백을 호출하기 전에 fetchData가 완료되는 즉시 테스트가 완료된다는 것입니다.
The problem is that the test will complete as soon as fetchData completes, before ever calling the callback.

이 문제를 해결하는 대체 테스트 형식이 있습니다. 빈 인수가 있는 함수에 테스트를 넣는 대신 done이라는 단일 인수를 사용하세요. Jest는 테스트를 완료하기 전에 done 콜백이 호출될 때까지 기다립니다.

//--asycn

function fetchData(cb) {
  var result;
  axios.get('http://localhost:3000').then((response) => {
    result = cb(response.data);
  });
  return result;
}

test('the data is hello', (done) => {
  function callback(data) {
    try {
      expect(data).toBe('hello');
      done();
    } catch (error) {
      done(error);
    }
  }

  fetchData(callback);
});

done()이 호출되지 않으면 테스트가 시간 초과 오류와 함께 실패합니다

expect 문이 실패하면 오류가 발생하고 done()이 호출되지 않습니다. 테스트 로그에서 실패한 이유를 보려면 try 블록에서 expect를 래핑하고 catch 블록에서 오류를 done으로 전달해야 합니다.

그렇지 않으면, expect(데이터)에 의해 수신된 값이 표시되지 않는 불투명한 시간 초과 오류가 발생합니다.

Promises

코드에서 Promise를 사용하는 경우 비동기 테스트를 처리하는 더 간단한 방법이 있습니다. 테스트에서 Promise를 반환하면 Jest는 해당 Promise가 해결될 때까지 기다립니다. Promise가 거부되면 테스트는 자동으로 실패합니다.

예를 들어, fetchData가 콜백을 사용하는 대신 문자열 'hello'로 해석되어야 하는 promise을 반환한다고 가정해 보겠습니다. 다음과 같이 테스트할 수 있습니다.

function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      resolve(response.data);
    });
  });
}

test('the data is hello', () => {
  return fetchData().then((data) => {
    expect(data).toBe('hello');
  });
});

promise을 반환해야 합니다. 이 반환 문을 생략하면 fetchData에서 반환된 promise이 해결되고 then()이 콜백을 실행할 기회가 있기 전에 테스트가 완료됩니다.

promise이 거부될 것으로 예상되는 경우 .catch 메서드를 사용합니다. 특정 수의 어설션이 호출되는지 확인하려면 expect.assertions를 추가해야 합니다. 그렇지 않으면 이행된 promise가 테스트에 실패하지 않습니다.

function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      reject('error');
    });
  });
}

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch((e) => expect(e).toMatch('error'));
});

.resolves / .rejects

또한 expect 문에서 .resolves matcher를 사용할 수 있으며 Jest는 해당 promise가 해결될 때까지 기다립니다. promise가 reject되면 테스트는 자동으로 실패합니다.

function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      resolve(response.data);
    });
  });
}

test('the data is hello', () => {
  return expect(fetchData()).resolves.toBe('hello');
});

어설션을 반환해야 합니다. 이 반환 문을 생략하면 fetchData에서 반환된 약속이 해결되고 then()이 콜백을 실행할 기회가 있기 전에 테스트가 완료됩니다.

promise가 reject될 것으로 예상되면 .rejects 매처를 사용하세요. .resolves matcher와 유사하게 작동합니다. promise가 이행되면 테스트는 자동으로 실패합니다.

function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      reject('error');
    });
  });
}

test('the fetch fails with an error', () => {
  return expect(fetchData()).rejects.toMatch('error');
});

Async/Await

대안으로 테스트에서 async 및 await를 사용할 수 있습니다. 비동기 테스트를 작성하려면 테스트에 전달된 함수 앞에 async 키워드를 사용합니다. 예를 들어 다음을 사용하여 동일한 fetchData 시나리오를 테스트할 수 있습니다.

function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      resolve(response.data);
    });
  });
}

test('the data is hello', async () => {
  const data = await fetchData();
  expect(data).toBe('hello');
});
});
function fetchData() {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:3000').then((response) => {
      reject('error');
    });
  });
}

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

async 및 await를 .resolves 또는 .rejects와 결합할 수 있습니다.

test('the data is hello', async () => {
  await expect(fetchData()).resolves.toBe('hello');
});

test('the fetch fails with an error', async () => {
  await expect(fetchData()).rejects.toMatch('error');
});

이러한 경우 async 및 await는 Promise 예제에서 사용하는 것과 동일한 논리에 대한 효과적인 구문 설탕입니다.

이러한 형식 중 어느 것도 다른 형식보다 특별히 우수하지 않으며 코드베이스 또는 단일 파일에서 혼합 및 일치시킬 수 있습니다. 테스트를 더 간단하게 만드는 스타일에 따라 다릅니다.

참고사이트

공식사이트 Testing Asynchronous Code v27.2

진행사항

### 초급

1. 공식 홈페이지에서 개념정리 
2. 공식 홈페이지에서 튜토리얼 or 가이드 실습하기 (진행)

### 중반

1. 실제로 프로젝트 만들기
2. 프로젝트 진행하면서 API DOC 찾아보며 정리하기

### 후반

1. 오픈소스 컨트리뷰트
반응형
Comments