非同期処理
非同期コードは、現代のJavaScriptアプリケーションでは一般的です。そのテスト方法は、同期コードのテストとほぼ同じですが、1つの重要な違いがあります。Jasmineは、非同期処理が完了した時点を知る必要があります。
Jasmineは、非同期処理の管理方法として3つの方法をサポートしています。async
/await
、Promise、そしてコールバックです。Jasmineがこれらのいずれかを検出しない場合、処理は同期処理であると仮定し、関数が返された直後にキュー内の次の処理に進みます。これらのメカニズムはすべて、beforeEach
、afterEach
、beforeAll
、afterAll
、およびit
で機能します。
async/await
通常、非同期テストを記述する最も便利な方法は、async
/await
を使用することです。async
関数は暗黙的にPromiseを返します。Jasmineは、返されたPromiseが解決または拒否されるまで待機してから、キュー内の次の処理に進みます。beforeAll
またはafterAll
の場合、拒否されたPromiseはスペックの失敗、またはスイートレベルの失敗を引き起こします。
beforeEach(async function() {
await someLongSetupFunction();
});
it('does a thing', async function() {
const result = await someAsyncFunction();
expect(result).toEqual(someExpectedValue);
});
Promise
より詳細な制御が必要な場合は、明示的にPromiseを返すことができます。Jasmineは、then
メソッドを持つオブジェクトをすべてPromiseとみなします。そのため、JavaScriptランタイムの組み込みPromise
型またはライブラリを使用できます。
beforeEach(function() {
return new Promise(function(resolve, reject) {
// do something asynchronous
resolve();
});
});
it('does a thing', function() {
return someAsyncFunction().then(function (result) {
expect(result).toEqual(someExpectedValue);
});
});
コールバック
コールバックを使用して非同期テストを記述することも可能です。これはより低レベルのメカニズムであり、エラーが発生しやすい傾向がありますが、コールバックベースのコードをテストする場合や、Promiseで表現するのが不便なテストの場合に役立ちます。Jasmineに渡される関数が引数(従来はdone
と呼ばれます)を受け取る場合、Jasmineは非同期処理が完了したときに呼び出される関数を渡します。
done
コールバックは正確に1回呼び出されることが不可欠であり、done
を呼び出すことは、非同期関数またはその関数が呼び出す関数の最後の処理である必要があります。コールバックスタイルの非同期テストを記述する際のよくある間違いは、テスト対象のコードがまだ実行されている間にdone
を呼び出すことです。その場合、done
が呼び出された後にスローされたエラーは、それを発生させたスペックとは異なるスペックに関連付けられるか、まったく報告されない可能性があります。
beforeEach(function(done) {
setTimeout(function() {
// do some stuff
done();
}, 100);
});
it('does a thing', function(done) {
someAsyncFunction(function(result) {
expect(result).toEqual(someExpectedValue);
done();
});
});
エラー処理
非同期コードで問題が発生することがあり、スペックが正しく失敗することを期待する場合があります。処理されないエラーはすべてJasmineによってキャッチされ、現在実行されているスペックに送信されます。スペックを明示的に失敗させる必要がある場合もあります。
Promiseによる失敗
拒否されたPromise
は、エラーのスローと同じように、スペックの失敗を引き起こします。
beforeEach(function() {
return somePromiseReturningFunction();
});
it('does a thing', function() {
// Since `.then` propagates rejections, this test will fail if
// the promise returned by asyncFunctionThatMightFail is rejected.
return asyncFunctionThatMightFail().then(function(value) {
// ...
});
});
function somePromiseReturningFunction() {
return new Promise(function(resolve, reject) {
if (everythingIsOk()) {
resolve();
} else {
reject();
}
});
}
async/awaitによる失敗
async
/await
関数は、拒否されたPromiseを返すか、エラーをスローすることによって失敗を示すことができます。
beforeEach(async function() {
// Will fail if the promise returned by
// someAsyncFunction is rejected.
await someAsyncFunction();
});
it('does a thing', async function() {
// Will fail if doSomethingThatMightThrow throws.
doSomethingThatMightThrow();
// Will fail if the promise returned by
// asyncFunctionThatMightFail is rejected.
const value = await asyncFunctionThatMightFail();
// ...
});
コールバックによる失敗
コールバックとして渡されるdone
関数も、オプションでメッセージまたはError
オブジェクトを渡すことで、done.fail()
を使用してスペックを失敗させるために使用できます。
beforeEach(function(done) {
setTimeout(function() {
try {
riskyThing();
done();
} catch (e) {
done.fail(e);
}
});
});
done
関数は、スペックを失敗させるために直接渡されたError
も検出します。
beforeEach(function(done) {
setTimeout(function() {
let err = null;
try {
riskyThing();
} catch (e) {
err = e;
}
done(err);
});
});
レポーター
レポーターイベントハンドラも、これらの方法のいずれかを使用して非同期にすることができます。すべてのレポーターイベントは既にデータを受信していることに注意してください。コールバックメソッドを使用している場合は、done
コールバックが最後のパラメータである必要があります。
非同期テストを書かずに済むようにモッククロックを使用する
操作がsetTimeout
またはその他の時間ベースの動作に依存しているという理由だけで非同期である場合、それをテストする良い方法は、Jasmineのモッククロックを使用して同期的に実行することです。このタイプのテストは記述が容易になり、時間が経過するのを実際に待つ非同期テストよりも高速に実行されます。
function doSomethingLater(callback) {
setTimeout(function() {
callback(12345);
}, 10000);
}
describe('doSomethingLater', function() {
beforeEach(function() {
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('does something after 10 seconds', function() {
const callback = jasmine.createSpy('callback');
doSomethingLater(callback);
jasmine.clock().tick(10000);
expect(callback).toHaveBeenCalledWith(12345);
});
});