今回はJavaScript テスティングフレームワーク「Jest」を使って非同期関数をテストします。

テストの基礎的な概念は公式ドキュメントで十分なのですが、公式ドキュメントには実際にデータを取得する非同期関数がなかったので実用的なサンプルコードを用意しました。

公式ドキュメント:非同期コードのテスト

尚、本記事はすでにjestとaxiosがインストールされている前提で解説を進めます。

npm install --save-dev jest
npm install axios

テスト要件

「郵便番号-住所検索API」を使って郵便番号から住所を取得する処理を実行し、戻り値の都道府県や市町村が期待される値に一致するかをチェックします。

今回は郵便番号「100-0014」の住所をレスポンスとして受け取ります。

■ レスポンスデータ。

    {
      pref: '東京都',
      address: '千代田区永田町',
      city: '千代田区',
      town: '永田町',
      fullAddress: '東京都千代田区永田町'
    }

※ 郵便番号-住所検索APIについて

郵便番号データをパラメータに含めてリクエストを送信することで、都道府県、市区町村のデータをJSONフォーマットで返却するシンプルなサービスです。

郵便番号-住所検索API

テストコード

非同期処理をテストするために、2つのテストを用意しました。

  1. 「Promise test」:Promise構文の非同期処理テスト
  2. 「async/await test」:async/await構文の非同期処理のテスト
const axios = require('axios').default;
const zipcode = '100-0014';

test('Promise test', async () => {
    return expect(promiseFetchData()).resolves.toBe("千代田区永田町")
});
async function promiseFetchData() {
    return new Promise((resolve, reject) => {
        axios.get(`https://api.zipaddress.net/?zipcode=${zipcode}`)
        .then((response) => {
            resolve(response.data.data.address)
        })
        .catch((error) => {
            reject(error)
        })
    })
}


test('async/await test', async () => {
    await expect(_fetchData()).resolves.toBe("永田町")
});
async function _fetchData() {
    return axios.get(`https://api.zipaddress.net/?zipcode=${zipcode}`)
    .then((response) => {
        return response.data.data.town;
    })
    .catch((error) => {
        throw new Error(error);
    })
}

実行結果

1つ目の「Promise test」では、郵便番号「100-0014」を送信して「address: ‘千代田区永田町’」を受け取りチェックします。

2つ目の「async/await test」では、郵便番号「100-0014」を送信して「town: ‘永田町’」を受け取りチェックします。

上記のテストコードを実行すると、2つともテストが成功しました。

Debugger attached.
 PASS  ./promise.test.js
  ✓ Promise test (162 ms)
  ✓ async/await test (48 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.617 s, estimated 1 s

構文

test('Promise test', async () => {
    return expect(promiseFetchData()).resolves.toBe("千代田区永田町")
});
test('async/await test', async () => {
    await expect(_fetchData()).resolves.toBe("永田町")
});

非同期関数をテストする場合には、testに渡す関数の前にasync キーワードを記述し、async / await を .resolves または .rejectマッチャと組み合わせることができます。

.resolves または .rejectマッチャを使用することで、Jestはpromiseが解決されるまで待機します。よってテストしたい値を非同期関数で取得する場合には、値の取得までテストは実行されません。

※expectの前にreturn awaitを記述すること

非同期処理でpromiseを返す関数の場合は、expectの前にreturnもしくはawaitを使用する必要があります。この記述を忘れると、promiseがresolveまたはrejectされる前に、テストが終了してしまいます。

テストを失敗させる

では、次にテストを失敗させたいので、呼び出すAPIのURLを壊します。Promise構文でテストします。

test('Promise test', async () => {
    return expect(promiseFetchData()).resolves.toBe("千代田区永田町")
});
async function promiseFetchData() {
    return new Promise((resolve, reject) => {
        axios.get(`https://api.address.net/?zipcode=${zipcode}`)
        .then((response) => {
            resolve(response.data.data.address)
        })
        .catch((error) => {
            reject(error)
        })
    })
}

テスト結果

↓テストが失敗し、エラーが出力されました。

● Promise test

    expect(received).resolves.toBe()

    Received promise rejected instead of resolved
    Rejected to value: [Error: getaddrinfo ENOTFOUND api.address.net]

      3 |
      4 | test('Promise test', async () => {
    > 5 |     return expect(promiseFetchData()).resolves.toBe("千代田区永田町")
        |            ^
      6 | });
      7 | async function promiseFetchData() {
      8 |     return new Promise((resolve, reject) => {

      at expect (node_modules/expect/build/index.js:128:15)
      at Object.expect (promise.test.js:5:12)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total
Snapshots:   0 total
Time:        2.094 s

内容を確認すると、非同期処理失敗のreject()が呼ばれ、続いてapi.address.netが見つからなかった旨のメッセージが表示されています。

今回は、あえてテストを失敗させるためにAPI呼び出しのURLを壊したので当然の結果となります。

API呼び出しのURLを正常に戻してテストすると、通過するはずです。

例外チェック

上章でテストをあえて失敗させましたが、

この章では「例外処理が発生する( = APIからデータ取得中にエラー発生)」をテスト通過条件として、例外処理が呼び出されたかどうかをテストします。

なぜこのようなテストを行うか。

フロントエンドアプリケーションを開発する過程で、API呼び出し中にエラーは発生します。ネットワーク不調の場合はAPI通信できませんし、APIをホストしているサーバーの調子が悪い場合にもAPI呼び出しが正常にできない可能性もあります。

そのような場合にアプリケーションが利用不可能な状態になれば、困るのはユーザーです。API通信中にエラーが発生した場合には、アプリケーションが今どのような状況なのかユーザーに通知しなければなりません。そのような観点から、エラー発生時の例外処理をテストすることには大きな意味があります。

それでは、例外処理のテストコードを書いていきましょう。先程と同じようにAPI通信するURLを壊します。

test('async/await test', async () => {
    await expect(_fetchData()).rejects.toThrow()
});
async function _fetchData() {
    return axios.get(`https://api.paddress.net/?zipcode=${zipcode}`)
    .then((response) => {
        return response.data.data.town;
    })
    .catch((error) => {
        throw new Error(error);
    })
}

今度は、テスト通過条件が変更されていることに気づくはずです。

await expect(_fetchData()).rejects.toThrow()

expect(_fetchData())でAPI通信してデータを取得しようとしますが、URLが壊れてるので通信はエラーになります。

非同期処理が失敗した場合には.catch()、Promiseの場合はreject()で例外処理を投げることができます。

.catch((error) => {
    throw new Error(error);
})

そして処理に失敗した場合には、rejects.toThrow()で例外処理が発生したことを検知できます。

rejects.toThrow()

テスト結果

テストは成功です。アプリケーションを開発する場合は、非同期処理にあえてエラーを発生させて例外処理がきちんと呼ばれるかもテストするべきなので、このテストは有効です。

 PASS  ./promise.test.js
  ✓ async/await test (19 ms)
  ○ skipped Promise test

Test Suites: 1 passed, 1 total
Tests:       1 skipped, 1 passed, 2 total
Snapshots:   0 total
Time:        0.365 s, estimated 1 s

関連記事

【JavaScript】非同期処理(Promise)とは何か

カテゴリー: JavaScriptテスト