Mocha 훅

Mocha 훅 (Hooks) — 테스트 전후 준비와 정리

테스트를 제대로 쓰려면 각 테스트가 독립적으로 돌도록, 시작 전에 환경을 준비하고 끝나면 치워 주는 일이 반복돼요. Mocha는 이걸 before() · after() · beforeEach() · afterEach() 네 가지 훅으로 처리해요. 이 훅들은 BDD 인터페이스의 기본 기능이라 대부분의 테스트에서 자연스럽게 쓰게 됩니다.

출처: Mocha — 훅 (Hooks)

네 가지 훅의 역할

  • before() — 그 블록의 첫 번째 테스트가 실행되기 전에 한 번만 실행돼요
  • after() — 그 블록의 마지막 테스트가 끝난 뒤 한 번만 실행돼요
  • beforeEach() — 그 블록 안의 각 테스트 전에 실행돼요
  • afterEach() — 그 블록 안의 각 테스트 후에 실행돼요
describe("hooks", function () {
  before(function () {
    // runs once before the first test in this block
  });

  after(function () {
    // runs once after the last test in this block
  });

  beforeEach(function () {
    // runs before each test in this block
  });

  afterEach(function () {
    // runs after each test in this block
  });

  // test cases
});

테스트는 훅의 앞에 놓아도, 뒤에 놓아도, 훅과 섞어 놓아도 상관없어요. 실행 순서는 정의된 순서를 따릅니다: 모든 before() 훅이 (한 번) 실행된 뒤, 각 테스트마다 beforeEach() → 테스트 → afterEach()가 돌고, 마지막에 after() 훅이 (한 번) 실행돼요.

훅에 설명 붙이기

어떤 훅이든 선택적으로 설명(description)을 붙일 수 있어요. 그러면 테스트가 실패했을 때 어느 훅이 문제인지 짚기 훨씬 쉬워져요. 이름 있는 함수를 넘기면 설명을 안 주어도 그 함수 이름이 대신 쓰여요.

beforeEach(function () {
  // beforeEach hook
});

beforeEach(function namedFun() {
  // beforeEach:namedFun
});

beforeEach("some description", function () {
  // beforeEach:some description
});

비동기 훅

네 가지 훅은 모두 일반 테스트 케이스처럼 동기이거나 비동기일 수 있어요. 예를 들어 각 테스트 전에 DB에 더미 데이터를 채우고 싶다면, 아래처럼 done 콜백을 쓰는 비동기 훅으로 만들 수 있죠.

describe("Connection", function () {
  const db = new Connection(),
    tobi = new User("tobi"),
    loki = new User("loki"),
    jane = new User("jane");

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

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

루트 레벨 훅

스위트(suite) 밖, 테스트 파일의 최상위 스코프에 정의된 훅을 **루트 훅(root hook)**이라고 불러요. 특별한 설정이 필요할 때 쓸 수 있는데, v8.0.0부터는 Root Hook Plugins가 이 루트 훅을 잡는 더 권장되는 방식이에요.

지연된 루트 스위트 (Delayed Root Suite)

:::caution 지연된 루트 스위트는 병렬 모드(parallel mode)와 함께 쓸 수 없어요. :::

어떤 스위트도 실행되기 전에 (예: 테스트를 동적으로 생성하기 위해) 비동기 작업을 먼저 해야 한다면, 루트 스위트의 실행을 지연시킬 수 있어요. mocha--delay 플래그와 함께 실행하면 됩니다. 이렇게 하면 전역 컨텍스트에 run()이라는 특별한 콜백 함수가 붙어요.

--delay가 붙으면 Mocha는 테스트 파일을 불러와 최상위 코드를 평소처럼 실행하되, run()을 호출하기 전까지는 테스트 실행을 기다려요. 그래서 describe()·it() 호출을 비동기 코드 안에 두어 셋업이 끝난 뒤 스위트가 등록되게 하고, 그 다음 run()을 불러 실행을 시작하는 방식이에요.

import assert from "node:assert";

const fn = async (x) => {
  return new Promise((resolve) => {
    setTimeout(resolve, 3000, 2 * x);
  });
};

// instead of an IIFE, you can use 'setImmediate' or 'nextTick' or 'setTimeout'
(async function () {
  const z = await fn(3);

  describe("my suite", function () {
    it(`expected value ${z}`, function () {
      assert.strictEqual(z, 6);
    });
  });

  run();
})();

더 알아보기 (Learn more)