Testing Asynchronous Code
JavaScriptではコードを非同期に実行することがよくあります。 非同期的に動作するコードがある場合、Jestはテスト対象のコードがいつ完了したかを別のテストに進む前に知る必要があります。 Jestはこのことを処理する方法をいくつか持っています。
コールバック
最も一般的な非同期処理のパターンはコールバックです。
例えば データを取得してcallback(data)
を呼び出すfetchData(callback)
関数があるとしましょう。 You want to test that this returned data is the string 'peanut butter'
.
By default, Jest tests complete once they reach the end of their execution. That means this test will not work as intended:
// 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
If your code uses promises, there is a more straightforward way to handle asynchronous tests. Return a promise from your test, and Jest will wait for that promise to resolve. promiseがrejectされた場合は、テストは自動的に失敗します。
For example, let's say that fetchData
, instead of using a callback, returns a promise that is supposed to resolve to the string 'peanut butter'
. We could test it with:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
promiseを返していることを確認してください。- もしこの return
文を省略した場合、あなたのテストは、fetchData
がresolveされpromiseが返ってくる前に実行され、then() 内のコールバックが実行される前に完了してしまいます。
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
You can also use the .resolves
matcher in your expect statement, and Jest will wait for that promise to resolve. 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');
});
Async/Await
また、async
と await
をテストで使用できます。 To write an async test, use the async
keyword in front of the function passed to test
. 例えば、同じ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');
}
});
You can combine async
and await
with .resolves
or .rejects
.
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.toThrow('error');
});
In these cases, async
and await
are effectively syntactic sugar for the same logic as the promises example uses.
None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style you feel makes your tests simpler.