Mocha에서 비동기 코드 테스트하기

Mocha에서 비동기 코드 테스트하기

자바스크립트 세계에서 테스트할 대상은 콜백을 쓰는 API, Promise를 반환하는 API, async/await 를 쓰는 코드 등 형태가 제각각이에요. Mocha는 이렇게 완료 시점이 늦게 오는 코드를 세 가지 방식으로 기다려 줍니다. 각 방식이 언제 쓰기 좋은지, 그리고 같이 쓰면 안 되는 조합이 무엇인지 짚어 볼게요.

출처: Mocha — 비동기 코드 테스트

done 콜백 방식

테스트 콜백에 인자(보통 done이라고 이름 지어요)를 하나 추가하면, Mocha는 그 함수가 실제로 호출될 때까지 테스트가 끝나지 않았다고 생각해요. 그래서 비동기 작업이 끝난 뒤 done()을 불러야 테스트가 비로소 완료되는 거죠.

describe("User", function () {
  describe("#save()", function () {
    it("should save without error", function (done) {
      const user = new User("Luna");
      user.save(function (err) {
        if (err) done(err);
        else done();
      });
    });
  });
});

done 콜백은 Error 인스턴스나 falsy 값을 받아요. 에러를 넘기면 보통 그 테스트는 실패로 처리되고, 그 외의 다른 값을 넘기면 잘못된 사용이라 예외가 발생합니다.

콜백이 에러 인자를 바로 넘겨주는 형태라면, 다음과 같이 done을 통째로 넘겨도 돼요. 에러가 있으면 자동으로 처리되거든요.

describe("User", function () {
  describe("#save()", function () {
    it("should save without error", function (done) {
      const user = new User("Luna");
      user.save(done);
    });
  });
});

Promise 반환 방식

콜백 대신 테스트 안에서 Promise를 반환하는 것도 가능해요. 테스트하려는 API가 콜백이 아니라 Promise를 쓰는 쪽이라면 이 방식이 훨씬 자연스럽죠.

beforeEach(function () {
  return db.clear().then(function () {
    return db.save([tobi, loki, jane]);
  });
});

describe("#find()", function () {
  it("respond with matching records", function () {
    return db.find({ type: "User" }).should.eventually.have.length(3);
  });
});

여기서 should.eventually 흐림은 Chai as Promised가 제공하는 검증 방식이에요. Promise가 resolve 된 뒤에 그 값을 이어서 검사할 수 있게 해 주죠.

Mocha v3.0.0부터는 Promise를 반환하면서 동시에 done()을 부르는 게 금지돼요. 둘 다 쓰는 건 보통 실수이기 때문이에요. 아래처럼 하면 Error: Resolution method is overspecified. 라는 에러로 테스트가 실패합니다. 참고로 v3.0.0 이전 버전에서는 이 done() 호출이 사실상 무시됐어요.

import assert from "node:assert";

// antipattern
it("should complete this test", function (done) {
  return new Promise(function (resolve) {
    assert.ok(true);
    resolve();
  }).then(done);
});

async / await 방식

실행 환경이 async/await 를 지원한다면, 아예 async 함수로 테스트를 쓸 수도 있어요. 코드가 가장 평범한 동기 코드처럼 읽혀서 깔끔해요.

beforeEach(async function () {
  await db.clear();
  await db.save([tobi, loki, jane]);
});

describe("#find()", function () {
  it("responds with matching records", async function () {
    const users = await db.find({ type: "User" });
    users.should.have.length(3);
  });
});

비동기 콜백의 제약

done, Promise, async/await 는 it()은 물론 before() · after() · beforeEach() · afterEach() 훅에서도 쓸 수 있어요. 하지만 describe()에서는 비동기 콜백을 쓸 수 없고 반드시 동기여야 해요. 자세한 내용은 관련 이슈에서 확인할 수 있어요.

동기 코드 테스트

테스트할 코드가 동기라면 콜백 인자를 아예 생략하면 돼요. 그러면 Mocha가 자동으로 다음 테스트로 넘어갑니다.

describe("Array", function () {
  describe("#indexOf()", function () {
    it("should return -1 when the value is not present", function () {
      [1, 2, 3].indexOf(5).should.equal(-1);
      [1, 2, 3].indexOf(0).should.equal(-1);
    });
  });
});

더 알아보기 (Learn more)