非同期処理

非同期コードは、現代のJavaScriptアプリケーションでは一般的です。そのテスト方法は、同期コードのテストとほぼ同じですが、1つの重要な違いがあります。Jasmineは、非同期処理が完了した時点を知る必要があります。

Jasmineは、非同期処理の管理方法として3つの方法をサポートしています。async/await、Promise、そしてコールバックです。Jasmineがこれらのいずれかを検出しない場合、処理は同期処理であると仮定し、関数が返された直後にキュー内の次の処理に進みます。これらのメカニズムはすべて、beforeEachafterEachbeforeAllafterAll、および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);
  });
});