[JEST] Jest로 비동기 코드 테스트 & 테스트 전후 작업

mhlog·2023년 5월 21일
0

Jest

목록 보기
2/3
post-thumbnail

Javascript로 코드를 작성하다보면 비동기 통신을 할 때가 굉장히 많다. Jest로 비동기 통신을 어떻게 테스트 할 수 있는 지 알아보자. 추가로 describe 문법과 테스트 전후에 해줄 수 있는 헬퍼에 대해서 알아보도록 하겠다.

1. Jest로 Callback 함수 테스트

다음과 같은 비동기 함수가 있다고 해보겠다.

// fn.js
const fn = {
  add: (num1, num2) => num1 + num2,
  getName: (callback) => {
    const name = "Mike";
    setTimeout(() => {
      callback(name);
    }, 3000)
  }
};

module.exports = fn;

위의 함수를 테스트하는 코드는 다음과 같이 작성할 수 있다.

// fn.test.js
const fn = require('./fn');

test('3초후 받아온 이름은 Mike', () => {
  function callback(name) {
    expect(name).toBe("Mike");
  }
  fn.getName(callback);
})

우리는 위의 함수를 Jest Runner로 실행시키면 대략 3초가 걸리고 Test가 통과할 것이라 예측할 수 있다.

1초만에 Test가 끝난 것을 확인할 수 있다. toBe문을 Mike가 아닌 다른 이름으로 바꾸면 실패를 예측하였는데 이 또한 성공한 것을 확인할 수 있었다.

이는 Jest Runner가 비동기임을 예측하지 못하고 callback 함수를 호출시키지 않는다고 유추해 볼 수 있다. 해결 방법은 test의 callback 함수에 done을 넣어서 명시적으로 비동기 코드임을 Jest Runner에게 알리는 방법이 있다. done이 호출되기 전까지 Jest는 테스트를 끝내지 않기 떄문에 callback 함수가 호출될 때까지 기다린 뒤 테스트를 마칠 수 있다. 수정된 코드는 다음과 같다.

// fn.test.js
const fn = require('./fn');

test('3초후 받아온 이름은 Mike', (done) => {
  function callback(name) {
    expect(name).toBe("Mike");
    done();
  }
  fn.getName(callback);
})


실행 결과 우리가 예상했던 것과 같이 대략 3초가 걸려 Test가 끝나는 것을 확인할 수 있다.

만약 done을 인자로 주고, done을 호출하지 않으면 5초 동안 기다린 뒤 응답이 없으면 실패로 간주한다.

2. Jest로 Promise 테스트

// fn.js
getAge: () => {
    const age = 30;
    return new Promise((res, rej) => {
      setTimeout(() => {
        res(age);
      }, 3000);
    })
  }

위의 fn 객체에 setTimeout으로 3초를 기다렸다가 Promise로 resolve된 나이를 반환하는 함수를 추가하였다. 위 함수에 대해서 test를 작성하면 다음과 같다.

// fn.test.js
const fn = require('./fn');

test('3초후 받아온 나이는 30', () => {
  fn.getAge().then(age => {
    expect(age).toBe(30);
  })
})

실행시키면 callback 함수와 같이 우리가 예상했던 대로 3초후에 실행이 되어야 하는데 그렇지 않은 것을 확인할 수 있다. toBe안의 인자를 30이 아닌 다른값으로 바꾸어 보아도 Test가 통과하는 것을 확인할 수 있다.

해결 방법은 return 문만 추가해주면 원했던 바와 같이 테스트가 수행된다. 테스트 함수가 Promise를 리턴하면 Jest Runner는 리턴된 Promise가 resolve될 때까지 기다려주기 때문이다. 수정된 코드는 다음과 같다.


예상과 같이 3초 이상의 시간이 걸려 test가 통과된 것을 확인할 수 있다.

다른 해결 방법으로는 resolves와 rejects 같은 Matcher를 사용할 수도 있다.

// fn.test.js
const fn = require('./fn');

test('3초후 받아온 나이는 30', () => {
  return expect(fn.getAge()).resolves.toBe(30)
});

Matcher를 사용하니 훨씬 더 코드가 간결해진 것을 확인할 수 있다. reject에 대한 Matcher는 rejects를 사용하면 된다. rejects에는 toBe보다는 toMatch를 통해서 error 메세지를 비교해주면 될 것이다.

마지막 방법으로는 async, await를 사용하여 Promise를 처리하는 것이다. 개인적으로 async, await가 익숙해서 그런지 가장 간편해보이는 코드였다.

test('3초후 받아온 나이는 30', async () => {
  const age = await fn.getAge();
  expect(age).toBe(30);
});

위와 같이 작성하거나 resolves Matcher를 사용하면

test('3초후 받아온 나이는 30', async () => {
  await expect(fn.getAge()).resolves.toBe(30);
});

위와 같이 사용할 수 있다.

3. Test 전 후에 쓰는 Helper

  1. beforeEach(fn, timeout): 이 파일의 각 테스트가 실행되기 전에 함수를 실행
  2. afterEach(fn, timeout): 이 파일의 각 테스트가 완료된 후 함수를 실행
  3. beforeAll(fn, timeout): 이 파일의 첫번째 테스트가 실행되기 전에 함수를 실행
  4. afterAll(fn, timeout): 이 파일의 마지막 테스트가 실행되기 전에 함수를 실행

Helper 사용 예시

  • 만약 Database에서 전 후에 UserDB에 접속하여 정보를 가져오고 정보를 가져온 후에는 DB의 접속을 끊어주는 Test를 작성한다고 가정해보자. 이럴 떄 사용하면 좋은 것이 afterEach와 같은 Helper이다.
// fn.js
const fn = {
  add: (num1, num2) => num1 + num2,
  connectUserDb: () => {
    return new Promise(res => {
      setTimeout(() => {
        res({
          name: "Mike",
          age: 30,
          gender: "male"
        })
      }, 500)
    })
  },
  disconnectUserDb: () => {
    return new Promise(res => {
      setTimeout(() => {
        res();
      }, 500)
    })
  }
};

module.exports = fn;
// fn.test.js
const fn = require('./fn');

let user;
beforeAll(async () => {
  user = await fn.connectUserDb();
})
afterAll(async () => {
  await fn.disconnectUserDb();
})

test("이름은 Mike", () => {
  expect(user.name).toBe("Mike");
})
test("나이는 30", () => {
  expect(user.age).toBe(30);
})
test("성별은 남성", () => {
  expect(user.gender).toBe("male");
})

예상과 같이 앞 뒤로 약 0.5초씩 setTimeout이 실행되어 1초 이상의 시간이 걸린 것을 확인할 수 있다.

4. Describe, it()

테스트 파일에 많은 수의 테스트 함수가 작성되어 있는 경우, 연관된 테스트 함수들끼리 그룹화해놓으면 코드를 읽기가 좋다. 다음과 같이 Jest의 describe() 함수를 통해 여러 개의 테스트 함수를 묶는 것이 가능하다. 이떄 test 대신 it을 사용해도 된다. 두 함수의 기능은 완전히 동일하다. 기존 많이 사용되었던 Mocha나 Jasmin 같은 테스트 라이브러리에서 함수명을 it()을 사용하였기 때문에, Jest에서도 it()을 test() 함수의 별칭으로 제공해주고 있다.

const fn = require('./fn');

describe("User DB 관련 작업", () => {
  let user;
  beforeAll(async () => {
    user = await fn.connectUserDb();
  })
  afterAll(async () => {
    await fn.disconnectUserDb();
  })

  it("이름은 Mike", () => {
    expect(user.name).toBe("Mike");
  })
  it("나이는 30", () => {
    expect(user.age).toBe(30);
  })
  it("성별은 남성", () => {
    expect(user.gender).toBe("male");
  })
})

Reference

Jest로 테스트 전/후 처리하기
비동기 코드 테스트 - 자바스크립트 테스트 프레임워크

0개의 댓글