Testing Asynchronous Code
JavaScriptではコードを非同期に実行することがよくあります。 非同期的に動作するコードがある場合、Jestはテスト対象のコードがいつ完了したかを別のテストに進む前に知る必要があります。 Jestはこのことを処理する方法をいくつか持っています。
コールバック #
最も一般的な非同期処理のパターンはコールバックです。
例えば データを取得してcallback(data)を呼び出すfetchData(callback)関数があるとしましょう。 返ってくるデータが単に'peanut butter'という文字列であることをテストしたいとします。
デフォルトでは、Jestのテストは一度最後まで実行したら完了します。つまり下記のテストは意図したとおりには動作しないのです。
// Don't do this! test('the data is peanut butter', () => { function callback(data) { expect(data).toBe('peanut butter'); } fetchData(callback); });
問題はfetchDataが完了した時点でテストも完了してしまい、コールバックが呼ばれないことです。
これを修正する別の形のtest があります。 テストを空の引数の関数の中に記述するのではなく、 doneという1つの引数を利用します。 Jestは テストを終了する前に、done コールバックが呼ばれるまで待ちます。
test('the data is peanut butter', done => { function callback(data) { expect(data).toBe('peanut butter'); done(); } fetchData(callback); });
done() が呼ばれない場合、お望み通りにテストが失敗します。
Promises #
promiseを利用するコードでは、非同期処理のテストを簡単に扱える方法があります。 テストからpromiseを返すとJestはpromiseが解決されるまで待機します。 promiseがrejectされた場合は、テストは自動的に失敗します。
例えば、fetchDataにおいて、コールバックを使用する代わりに 'peanut butter'文字列を返すと思われるpromiseを返すことにしましょう。以下のようにテストすることができます:
test('the data is peanut butter', () => { expect.assertions(1); return fetchData().then(data => { expect(data).toBe('peanut butter'); }); });
必ずpromiseを返して下さい - return 宣言を書かなかった場合、fetchDataが完了する前にテストが終了します。
promiseがrejectされることを期待するケースでは .catch メソッドを使用してください。 想定した数のアサーションが呼ばれたことを確認するため、expect.assertionsを必ず追加して下さい。 さもなければpromiseがrejectされなかった場合にテストが失敗したと判定されません。
test('the fetch fails with an error', () => { expect.assertions(1); return fetchData().catch(e => expect(e).toMatch('error') ); });
.resolves / .rejects #
Jest バージョン20.0.0+で利用可能 #
expect宣言で .resolves マッチャを使うこともでき、Jestはそのpromiseが解決されるまで待機します。promiseがrejectされた場合、テストは自動的に失敗します。
test('the data is peanut butter', () => { expect.assertions(1); return expect(fetchData()).resolves.toBe('peanut butter'); });
必ずアサーションを返して下さい - このreturn 宣言を書かなかった場合、fetchDataが完了する前にテストが終了します。
promiseがrejectされることを期待するケースでは.rejects マッチャを使用してください。 .resolvesマッチャと似た動作をします。 promiseが成功した場合は、テストは自動的に失敗します。
test('the fetch fails with an error', () => { expect.assertions(1); return expect(fetchData()).rejects.toMatch('error'); });
Async/Await #
また、async と awaitをテストで使用できます。 非同期テストを書くには、 testに渡す関数の前にasync キーワードを記述するだけです。 例えば、同じfetchData シナリオは次のようにテストできます:
test('the data is peanut butter', async () => { expect.assertions(1); 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 または .rejectsと連結することもできます(Jestバージョン 20.0.0+で利用可能)。
test('the data is peanut butter', async () => { expect.assertions(1); await expect(fetchData()).resolves.toBe('peanut butter'); }); test('the fetch fails with an error', async () => { expect.assertions(1); await expect(fetchData()).rejects.toMatch('error'); });
これらのケースでは async や await は事実上、promiseを使用した例と同じロジックの単なる糖衣構文です。
これらの形式のどれかが他よりも優れているということはなく、コードベースや場合によっては同じファイル内でも混在して合わせて使うことができます。どのスタイルでテストを簡単にできるか次第なのです。