🙆

Jestで非同期のテスト方法について

に公開

はじめに

Jestの基礎はざっくり理解したので、非同期テスト方法の方法ついて学びたいと思いました。
Jestの基礎がある程度分かっている方が対象になります。

この記事で学べること

async/awaitを使用した非同期の場合や、コールバック関数を使用した場合のテストケースの作成方法について学べます。

動作環境

  • OS:macOS Sequoia 15.1.1
  • Node.js:22.6.0
  • npm:10.8.2
  • Jest:29.7.0

インストール方法

Jestの環境構築についてはこちらをご確認ください。

基本的な使い方

非同期のテストの場合はtest関数にasync/awaitを付ける必要がある
Promiseオブジェクトを直接返す場合はtest関数にasync/awaitを付ける必要はありません。
async/awaitを付けると、test関数は非同期関数として扱われます。

// 引数によってPromissのresolveかrejectを返す
function fetchData(success = true) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve("peanut butter");
      } else {
        reject("error");
      }
    }, 100);
  });
}
// Promiseオブジェクトをreturnしていればasync,awaitは必要無い
test("peanut butterが返ってくる事を確認", () => {
  return fetchData().then((data) => {
    expect(data).toBe("peanut butter");
  });
});

// async/awaitを付けていないと、setTimeoutが終わる前にテストが終了してしまうのでエラー
test("peanut butterが返ってくる事を確認",() => {
  const data = fetchData();
  expect(data).toBe("peanut butter");
});

test("エラーが返ってくる事を確認", async () => {
  expect.assertions(1); // このテストでは1つのexpectが実行されることを宣言
  try {
    await fetchData(false);
  } catch (error) {
    expect(typeof error).toBe("string");
  }
});

rejectのテストをする場合は、expect.assertionsを必ず指定する必要があります。
try/catchを使用してテストする場合はexpect.assertionsを書かないとexpectが一度も実行されずテストが通過してしまう事があります。

非同期関数用に用意されたマッチャーを使用すれば、以下のように書くこともできます。
この場合はPromiseオブジェクトをreturnするのでasync/awaitは必要ないです。

// .resolvesマッチャーを使ったテスト(Promiseの成功を確認)
test("peanut butterが返ってくる事を確認(.resolves)", () => {
  return expect(fetchData()).resolves.toBe("peanut butter");
});
// .rejectsマッチャーを使ったテスト(Promiseの失敗を確認)
test("エラーが返ってくる事を確認(.rejects)", () => {
  return expect(fetchData(false)).rejects.toMatch("error");
});

try/catchを使った場合は複数のアサーションを使う場合に使用し、1つのアサーションの場合は非同期関数のマッチャーを使用したら良いかと思います。

コールバック関数のテスト方法

// コールバック関数を受け取る関数
function fetchDataWithCallback(callback) {
  setTimeout(() => {
    callback("peanut butter",);
  }, 100);
}

test("コールバック関数のテスト", (done) => {
  function callback(data) {
    try {
      expect(data).toBe("peanut butter");
      done(); // テスト成功時に完了を通知
    } catch (error) {
      done(error); // 失敗したらエラーを渡して失敗を通知
    }
  }

  fetchDataWithCallback(callback);
});

コールバック関数のテストはdoneを付けないとfetchDataWithCallbackが呼ばれた時点でテストが終了してしまいcallbackが実行されなくなってしまう。

doneの通知を見てみる

以下で意図的に失敗させてdoneはなぜ必要なのか確認します。

test("コールバック関数のテスト", (done) => {
  function callback(data) {
    try {
      expect(data).toBe("wrong value"); // 意図的に失敗させる
      done(); // テスト成功時に完了を通知
    } catch (error) {
      done(error); // 失敗したらエラーを渡して失敗を通知
    }
  }

  fetchDataWithCallback(callback);
});

ログの内容

> test
> jest throw.test.js

 FAIL  ./throw.test.js
  ✕ コールバック関数のテスト (116 ms)

  ● コールバック関数のテスト

    expect(received).toBe(expected) // Object.is equality

    Expected: "wrong value"
    Received: "peanut butter"

      10 |     try {
      11 |       // expect(data).toBe("peanut butter");
    > 12 |       expect(data).toBe("wrong value"); // 意図的に失敗させる
         |                    ^
      13 |       done(); // テスト成功時に完了を通知
      14 |     } catch (error) {
      15 |       done(error); // 失敗したらエラーを渡して失敗を通知

      at toBe (throw.test.js:12:20)
      at Timeout.callback [as _onTimeout] (throw.test.js:4:5)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.902 s, estimated 1 s

done(error)を指定している事で、ExpectedReceivedが表示されています。
done(error)がないと引数の値は表示されない為、失敗時にエラーを渡してくれるdone(error)は必ず指定するようにしましょう。

ハマりやすい点・注意点

コールバック関数の場合にdoneを付け忘れてしまったり、try/catchを使用したrejectのテスト時にexpect.assertionsを付け忘れてしまうとテストがそのまま通ってしまうのでそこだけ注意が必要だと感じました。

まとめ

今回は非同期のテストケース作成方法について学びました。
基本の書き方は普通のテストケースとほぼ変わらず、Promiseオブジェクトを返す場合と返さない場合で少し書き方が変わる事と、コールバック関数の場合はdoneを付けると覚えておけば何とかやっていけそうな気がしました。

参考資料

Discussion