Testing Asynchronous Code

정민교·2023년 7월 16일
0

jest

목록 보기
3/5

📒

자바스크립트 코드는 비동기로 수행된다.

테스트 코드에 비동기로 동작해야하는 코드가 있다면, jest가 다른 테스트로 넘어가기 전에 현재 테스트가 완료되었음을 알아야 할 필요가 있다.

jest이는 이를 해결하기 위한 몇 가지 방법이 있다.

✔️Promises

테스트에 포함된 코드(함수)가 promise를 반환한다면 jest는 이 promise가 resolve 혹은 reject될 때까지 기다렸다가 수행된다.

reject 될 때는 테스트는 실패한다.

예를 들어fetchData() 함수가 promise를 반환하고 resolve되면 'peanut butter'문자열을 반환한다고 할 때 다음과 같이 작성할 수 있다.

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

✔️Async/Await

asyncawait을 사용할 수도 있다.

async 테스트를 위해서는 test에 전달할 콜백함수에 async 키워드를 붙여줘야 한다. 위 예시와 동일한 코드는 다음과 같이 작성할 수 있다.

test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

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

asyncawait.resolves 혹은 .jejects로 연결할 수 있다.

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

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

🚨반드시 promise를 반환하거나 await을 사용해야 한다. return/await 문을 생략하면 fetchData가 반환하는 promise가 resolve혹은 reject 되기 전에 테스트 코드가 완료될 것이다.

promise가 reject 될 것이 예상되는 코드라면, .catch 메서드를 사용해도 된다.

특정 횟수만큼의 assertions가 호출되는지 검증하기 위해 expect.assertions를 사용해야 한다.

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

✔️Callbacks

promise를 사용하지 않고 callback을 사용해도 된다.

이번에는 fetchData가 promise를 반환하지 않고 callback(null, data) 형태의 콜백함수를 전달받아야 한다고 가정하자. 즉 어떤 데이터를 가져오는 작업이 완료되면 fetchDatacallback을 호출하는 것이다.

그리고 이 함수가 문자열 "peanut butter"를 반환한다고 가정한다.

기본적으로 jest는 테스트 코드가 실행흐름 끝에 도달하면(실행이 끝나면) 테스트가 완료되었다고 본다.

test('반환된 데이터는 peanut butter다', () => {
  function callback(error, data) {
    if (error) {
      throw error;
    }
    expect(data).toBe('peanut butter');
  }

  fetchData(callback); // Jest가 콜백이 호출되기 전에 테스트가 완료될 가능성이 높다.
});

위와 같이 작성하면 테스트는 콜백이 호출되기 전에 종료될 수 있다.

따라서 jest한테 비동기 처리가 완료되면 콜백을 호출할 것임을 알려야 한다. 이를 위해 테스트 코드를 작성한 콜백 함수에 done 매개변수를 사용해야 한다.

test('반환된 데이터는 peanut butter다', (done) => {
  function callback(data) {
    if (error) {
      done(error);
      return;
    }
    try {
      expect(data).toBe('peanut butter');
      done(); // 테스트가 완료되었음을 Jest에게 알려줌
    } catch (error) {
      done(error); // 에러 발생 시 Jest에게 에러 전달
    }
  }

  fetchData(callback);
});

이렇게 작성하면 jest는 done 함수가 호출될 때까지 테스트를 완료하지 않는다. 그래서 테스트 코드에 작성한 콜백 함수 내의 검증 코드 expectmatcher가 테스트 완료 전에 제대로 호출된다.

done()함수가 호출되지 않는다면 테스트가 실패(timeout error)할 것이다.

🚨jest는 테스트 함수에서 done()콜백을 사용하거나 프로미스를 반환하여 비동기 동작을 나타내야 한다.
동일한 테스트에서 두 가지 방법을 모두 사용하면 테스트에서 원치 않는 동작과 메모리 누수가 발생할 수 있다.
jest는 테스트 코드에 done을 전달하고 테스트에서 코드 내에서 프로미스를 반환하려고 하면 오류를 발생시켜 가능한 혼란과 메모리 누수를 방지한다.

test('done과 프로미스의 잘못된 혼합', (done) => {
  fetchData().then((data) => {
    expect(data).toBe('peanut butter');
    done();
  });
  return fetchData(); // Jest가 허용하지 않습니다
});

✔️.resolves/.rejects

expect 문에서 .resolves matcher를 사용할 수 있다. 이러면 jest는 해당 프로미스가 resolve 될 때까지 기다린다.

프로미스가 reject 되면 테스트가 자동으로 실패한다.

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

반드시 assertion을 return해야 한다. 이 반환문을 생략하면 fecthData에서 반환된 프로미스가 resolve되기 전에 테스트가 완료되고 then()이 콜백을 실행할 기회조차 없다.

프로미스가 reject될 것 같다면 .rejects matcher를 사용하면 된다. .resolves와 유사하게 동작한다. 만약 프로미스가 fulfilled 되면 테스트는 자동으로 실패한다.

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

프로피스가 fullfilled 되거나 다른 값과 함께 reject되면 테스트가 실패한다.

profile
백엔드 개발자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN