非同期コードのテスト
JavaScriptではコードを非同期に実行することがよくあります。 非同期的に動作するコードがある場合、Jestはテスト対象のコードがいつ完了したかを別のテストに進む前に知る必要があります。 Jestはこのことを処理する方法をいくつか持っています。
Promises
テストからpromiseを返すと、Jestはそのpromiseがresolveされるまで待機します。 promiseがrejectされると、テストが失敗します。
例えば、fetchDataが'peanut butter'という文字列でresolveされるpromiseを返すとします。 以下のようにテストすることができます:
test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});
Async/Await
また、async と awaitをテストで使用できます。 非同期テストを書くには、 testに渡す関数の前にasync キーワードを記述するだけです。 例えば、同じfetchData シナリオは次のようにテストできます:
test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});
async と await を .resolves または .reject と組み合わせることができます。
test('the data is peanut butter', async () => {
  await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
  await expect(fetchData()).rejects.toMatch('error');
});
これらのケースでは async や await は事実上、promiseを使用した例と同じロジックの糖衣構文です。
promiseを返す、またはawaitするようにしましょう。returnまたはawaitを省いた場合、fetchDataから返されるpromiseがresolveまたはrejectされる前に、テストが終了してしまいます。
promiseがrejectされることを期待するケースでは .catch メソッドを使用してください。 想定した数のアサーションが呼ばれたことを確認するため、expect.assertionsを必ず追加して下さい。 Otherwise, a fulfilled promise would not fail the test.
test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});
コールバック
promiseを使わない場合、コールバックが使えます。 例えば、fetchDataがpromiseを返すのではなく、コールバックを使うとします。つまり、データを取得し終わったら、callback(null, data)を呼ぶとします。 返ってくるデータが'peanut butter'という文字列であることをテストしたいとします。
デフォルトでは、Jestのテストは一度最後まで実行したら完了します。 つまり下記のテストは意図したとおりには動作しないのです。
// 実行しないでください!
test('the data is peanut butter', () => {
  function callback(error, data) {
    if (error) {
      throw error;
    }
    expect(data).toBe('peanut butter');
  }
  fetchData(callback);
});
問題はfetchDataが完了した時点でテストも完了してしまい、コールバックが呼ばれないことです。
これを修正する別の形のtest があります。 テストを空の引数の関数の中に記述するのではなく、 doneという1つの引数を利用します。 Jestは テストを終了する前に、done コールバックが呼ばれるまで待ちます。
test('the data is peanut butter', done => {
  function callback(error, data) {
    if (error) {
      done(error);
      return;
    }
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }
  fetchData(callback);
});
done() が呼ばれない場合、お望み通りにテストが(タイムアウトにより)失敗します。
expect 文が失敗した場合、エラーがスローされて done() は呼び出されません。 テストログで失敗した理由を確認したい場合。 tryブロックでexpect をラップし、 catch ブロック内でエラーを done に渡す必要があります。 そうしなければ、 expect(data) によってどの値が受信されたかを示さない不透明なタイムアウトエラーが起こるだけになります。
Jest will throw an error, if the same test function is passed a done() callback and returns a promise. This is done as a precaution to avoid memory leaks in your tests.
.resolves / .rejects
expect宣言で .resolves マッチャを使うこともでき、Jestはそのpromiseが解決されるまで待機します。 promiseがrejectされた場合は、テストは自動的に失敗します。
test('the data is peanut butter', () => {
  return expect(fetchData()).resolves.toBe('peanut butter');
});
- もしこの return文を省略した場合、あなたのテストは、fetchDataがresolveされpromiseが返ってくる前に実行され、then() 内のコールバックが実行される前に完了してしまいます。
promiseがrejectされることを期待するケースでは.rejects マッチャを使用してください。 .resolvesマッチャと似た動作をします。 promiseが成功した場合は、テストは自動的に失敗します。
test('the fetch fails with an error', () => {
  return expect(fetchData()).rejects.toMatch('error');
});
これらの形式のどれかが他よりも優れているということはなく、コードベースや場合によっては同じファイル内でも混在して合わせて使うことができます。 どのスタイルでテストがシンプルになったと感じるかなのです。