// example.js

export function asyncFn() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Done!')
    }, 2000)
  })
}

다음과 같은 비동기 코드를 테스트하여 보자.

import { asyncFn } from './example'

describe('async test', () => {
  test('done', () => {
    asyncFn().then(res => {
      expect(res).toBe('Done') // 분명 'Done!'이어야 하는데?
    })
  })
})

이상하게 테스트가 통과되는 것을 볼 수 있다. 비동기 테스트를 진행하기 위해서는 다른 방식을 사용해야 하는 것을 알 수 있다.

done

비동기 동작이 언제 완료되는지를 알려주는 매개변수이다.

일단 위의 테스트가 분명 실패했어야 하는데도 성공했던 이유는 비동기를 기다리지 않고 바로 넘어가버려서 정상적으로 테스트가 동작하지 않았기 때문이었다. 이를 막기 위해 비동기 코드가 실행되고 나서 테스트가 종료되도록 강제할 필요가 있다.

test 함수의 매개변수로 done을 받은 후에 테스트 코드 밑에서 done() 함수를 호출시킨다.

비동기 코드가 실행된 이후에 done이 실행이 되고, 그 다음에 테스트가 동작된다. 따라서 done을 사용하면 정상적으로 테스트가 동작한다.

import { asyncFn } from './example'

describe('async test', () => {
  test('done', (done) => {
    asyncFn().then(res => {
      expect(res).toBe('Done!')
			**done()**
    })
  })
})

만약 테스트가 실패한다면 expect와 toBe는 에러를 던져 주고, 그 밑의 done()은 실행되지 않는다.

test('done', done => {
  asyncFn().then(res => {
    expect(res).toBe('Done1') // fail
    done()
    console.log('done done') // console에 찍히지 않는다.
  })
})

따라서 에러 핸들링을 제대로 해 주기 위해서는 catch 문을 활용하도록 하자.

test('done', done => {
  asyncFn()
    .then(res => {
      expect(res).toBe('Done')
      done()
    })
    .catch(error => {
      // done(error)
      console.error(error)
    })
})

return

done 말고도 비동기 코드를 테스트할 수 있는 방식은 여럿 있다. 가장 간단한 방법은 테스트에서 프로미스를 리턴해주는 것. 실제 이 비동기 함수(pending 상태의 프로미스)를 리턴해서 Jest가 직접 프로미스가 resolve될 때까지 기다렸다가 테스트를 진행할 수 있도록 해 준다.

이 경우에는 프로미스가 resolve될 때까지의 약간의 시간이 지나야 테스트가 완료된다.

test('then', () => {
  return asyncFn().then(res => {
    expect(res).toBe('Done?')
  }) **// 이 친구는 pending된 프로미스이다.**
})

resolves

resolves는 expect에서 주는 프로미스가 resolve될 때까지 기다렸다가 뒤의 toBe 메서드를 실행한다. 이 때 꼭 return문을 사용해주어야 한다. 그렇지 않는다면 프로미스가 리턴되기 전에 테스트 함수가 끝나버려서 제대로 된 테스트를 할 수 없다.

test('resolves', () => {
  return expect(asyncFn()).resolves.toBe('Done?')
})

만약 return문을 사용하지 않는 경우

test('resolves', () => {
  expect(asyncFn()).resolves.toBe('Done') // pass가 나온다.
})

async await

내 생각에 가장 깔끔하고 명시적인 코드가 아닌가 생각한다. 그냥 test 함수의 콜백 함수를 async 처리하고, 비동기 함수를 await으로 처리해 그 결과값을 테스트하는 방식이다.

test('async/await', async () => {
  const res = await asyncFn()
  expect(res).toBe('Done!')
})

Jest로 비동기 테스트 시 주의사항

Untitled

위의 코드에서는 setTimeOut이 2초로 설정되어 있다. 즉 비동기 코드가 2초의 시간을 두고 실행된다는 뜻이다. 이에 따라 실제 테스트 역시 2초 뒤에 테스트가 실행되게 된다.

그런데 만약 setTimeOut을 6초로 해 놓으면 테스트가 실패하는데, 이는 test 함수가 최대한으로 기다릴 수 있는 시간이 5초로 설정되어있기 때문이다.

export function asyncFn() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Done!')
    }, 6000)
  })
}

Untitled

이 때 사용하는 것이 바로 test 함수의 3번째 인자이다. 이는 test 함수가 max로 기다릴 수 있는 시간을 나타낸다. 밑의 코드처럼 test 함수의 최대 대기 시간을 7초로 해 놓으면 문제 없이 테스트가 진행되는 것을 알 수 있다.

import { asyncFn } from './example'

describe('async test', () => {
  test('async/await', async () => {
    const res = await asyncFn()
    expect(res).toBe('Done!')
  }, **7000**) // 여기
})

Untitled

이 때 만약 다시 비동기 작업의 시간을 2초로 해 놓으면 다시 테스트 시간도 2초가 되는 것을 알 수 있다. 그냥 기다리는 시간의 max 시간을 설정해놓는 인자라로 보면 될듯.