본문으로 바로가기

https://jestjs.io/docs/asynchronous

 

Testing Asynchronous Code · Jest

It's common in JavaScript for code to run asynchronously. When you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest has several ways to handle this.

jestjs.io

 

비동기 테스트를 잘못 짜면 아래와 같은 경고 문구가 터미널에 출력됩니다.

 

Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

 

테스트는 끝났는데 jest가 exit하지 못했다는 거죠. 보통 이 경우에는 비동기가 제대로 실행되지 못하고 테스트가 마쳐졌음을 의미합니다. 쉽게 말하자면 "비동기 동작 테스트하려고 기다렸다가 반환되는 값을 보려고 하는데 왜 값을 확인하기도 전에 테스트가 끝나냐?"입니다.

 

그렇다면 어떻게 짜야하는 걸까요? 문서를 따라가봅시다.

 

 

콜백 테스트하기

 

기본적으로 jest는 함수가 실행되면, 끝나게 됩니다. 그러니까, 아래와 같은 코드에서 콜백은 실행조차 되지 않는 겁니다.

makeStr 함수가 실행되고, 콜백이 호출되기도 전에 테스트가 끝나는 겁니다.

function makeStr(name, money, cb) {
  setTimeout(() => {
    const str = `${name} has ${money}`;
    return cb(str);
  }, 500);
}


// wrong!!!
test('should ', () => {
  makeStr('darren', 10000, str => {
    expect(str).toBe('darren has 200'); // pass???
  });
});

 

Instead of putting the test in a function with an empty argument, use a single argument called done. Jest will wait until the done callback is called before finishing the test. 그러니까 아래처럼 고치면 되겠군요.

// yes!!!
test('should ', done => {
  makeStr('darren', 10000, str => {
    try {
      expect(str).toBe('darren has 10000');
      done();
    } catch (error) {
      done(error);
    }
  });
});

 

 

Promise 테스트하기

Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.

Promise는 테스트 코드 내에 return 해주기만 하면 된다네요. resolve되면 pass, reject되면 fail됩니다.

function asyncStr(name) {
  return new Promise(resolve => {
    setTimeout(() => {
      const str = `my name is ${name}`;
      resolve(str);
    }, 1500);
  });
}

test('async test', () => {
  return asyncStr('darren').then(res => {
    expect(res).toBe('my name is darren');
  });
});

 

또 다른 방법으론 아래와 같이, resolves를 통해서 promise를 테스트할 수도 있습니다.  개인적으론 이것도 깔끔해서 좋아 보입니다.

다만 여기서도 반드시 return 해주어야 Promise를 제대로 테스트할 수 있습니다.

test('async test', () => {
  return expect(asyncStr('darren')).resolves.toBe('my name is darren');
});

 

만약, 고의적으로 reject를 내도록 코드를 짰다면 아래와 같이 짤 수 있습니다. error가 아니라 reject니까 toThrow가 아닌 toMatch를 썼습니다.

function asyncStr(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('no');
    }, 1500);
  });
}

test('async test', () => {
  return asyncStr('darren').then(res => {
      expect(res).toBe('my name is darren');
    })
    .catch(error => expect(error).toMatch('no'));
});

 

 

async/await 를 이용하여 비동기 테스트하기

 

매우 직관적입니다.

import 'regenerator-runtime/runtime';

function asyncStr(name) {
  return new Promise(resolve => {
    setTimeout(() => {
      const str = `my name is ${name}`;
      resolve(str);
    }, 1500);
  });
}

test('async test', async () => {
  const str = await asyncStr('darren');
  expect(str).toBe('my name is darren');
});

 

아래와 같은 꼴로도 테스트할 수 있습니다.

test('async test', async () => {
  await expect(asyncStr('darren')).resolves.toBe('my name is darren');
});

 

 

troubleshooting

 

* async/await를 이용한 비동기 테스트 도중 ReferenceError: regeneratorRuntime is not defined 에러가 난다?

 

폴리필 넣어줘야죠 뭐... 아, 근데 babel-pollyfill은 deprecated되었습니다. 

regenerator-runtime/runtime을 삽입해주면 됩니다.

yarn add regenerator-runtime --dev // 설치
import 'regenerator-runtime/runtime'; // 삽입

darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체