Reduxの非同期アクションをaxios-mock-adapterとredux-mock-storeでテストする

# やること
Jestを使って、ReduxのAction、特にHTTPリクエストなどの非同期処理が絡む部分をテストしたい。
特に今回の場合、すべてのActionがStoreからdispatchされていることを確認したいとする。

自分の場合、HTTPリクエストのライブラリは`axios`を使用している。そのため今回のテストでは、`axios`を使ったリクエストをモックできる`axios-mock-adapter`というライブラリを使用する。

モックというのは直訳で「マネする」の意。つまり、`axios`のリクエストをモックする、という言い方は「axiosのリクエストを擬似的に送って、擬似的なレスポンスを受け取る処理を行う」というような意味合いになる。

そのため、`axios-mock-adapter`のようなHTTPリクエストのモックライブラリを使うことで、実際のリクエストを送ることなく以下の点などを確かめることができる。

1. 対象のエンドポイントに正しくリクエストが送れるか。
2. 帰ってきたレスポンスをもとに後続処理が継続できるか

また、今回は全てのアクションが正しくdispatchされていることを確認するのが目的のため、`redux-mock-store`というライブラリを使用する。これを使うと、ReduxのStoreをモックして、dispatchされたアクションを確認することができる。Jestと併用することで実際にdispatchされたアクションと期待するアクションを比較したりできる。

# 前提
以下のような非同期のアクションがあるとする（解説のため処理を簡略化）。

```auth.js
import axios from 'axios';

export const auth = authData => {
    return dispatch => {
        const url = `http:/localhost:3001/api/auth/sign_in`;
        // Promiseを返すようにするのを忘れずに
        return axios
            .post(url, authData)
            .then(response => {
                dispatch(authSuccess(response.data));
            })
            .catch(error => {
                dispatch(authFail(error));
            });
    };
};
```
メールアドレスとパスワードを渡してエンドポイントにPOSTし、認証情報があっていれば認証成功となりStoreにユーザー情報を格納する。間違っていればエラー情報をStoreに格納する。
このアクションをテストしてみる。


# 手順
## axios-mock-adapter
まず`axios-mock-adapter`をプロジェクトに導入する。

公式ドキュメントを見ればだいたい分かる。
https://www.npmjs.com/package/axios-mock-adapter


```terminal
$ npm install --save-dev axios-mock-adapter
```


`auth.test.js`ファイルを作成し、axios-mock-adapterをインポート。
セットアップに`axios`が必要になるので一緒にインポートする。

```auth.test.js
import axios from 'axios';
import axiosMockAdapter from 'axios-mock-adapter';
```

adapterのモックを作成する。

```auth.test.js
const axiosMock = new axiosMockAdapter(axios);
```
引数に`axios`を渡す必要があるため先述の通りインポートした。

今回はPOSTリクエストを投げるので、以下の形式でモックのリクエストを記述する。

```auth.test.js
//HTTPリクエストを送るエンドポイント
const url = `http://localhost:3001/api/auth/sign_in`;

//POST時に渡すデータ
const authData = {
    email: 'test@example.com',
    password: 'password'
}

//帰ってくるレスポンス
const mockResponse = {
    token: 'abcdefg',
    uid: 1,
}

//モックのPOSTリクエストを投げる
axiosMock.onPost(
    url, //リクエスト先URL
    authData //POSTするデータ
).reply(
    200, //ステータス
    mockResponse //レスポンス
    // [第三引数にHeadersの情報も載せられる]
);
```
他のメソッドやオプションについては公式ドキュメントを参照。
https://www.npmjs.com/package/axios-mock-adapter


## redux-mock-store
同じくredux-mock-storeを導入する。

公式ドキュメント。
https://www.npmjs.com/package/redux-mock-store

```terminal
$ npm install --save-dev redux-mock-store
```

インポートする。Storeを作る際にmiddlewareを渡すので`redux-thunk`も併せてインポート。

```auth.test.js
import thunk from 'redux-thunk'; //セットアップに必要
import configureStore from 'redux-mock-store';
```

Storeを作成する。必要であれば`mockStore({})`にはinitial stateを設定することも可能。

```auth.test.js
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const store = mockStore({});
```

これでモックのStoreの情報を以下のように操作できる。

```hoge.js
store.dispatch(action) //モックのstoreからアクションをdispatchする
store.getActions() //dispatchされたアクションが配列で返ってくる
store.getState() //Storeのstateがオブジェクトで返ってくる
```

これも公式ドキュメントで詳細を参照。
https://www.npmjs.com/package/redux-mock-store

## テストファイル
最終的にテストファイルは以下のようになる。

```auth.test.js
import * as actionTypes from './actionTypes';
import * as actions from './auth';
import axios from 'axios';
import axiosMockAdapter from 'axios-mock-adapter';

describe('async actions', () => {
    const axiosMock = new axiosMockAdapter(axios);

    it('creates AUTH_SUCCESS if authentication succeeds', () => {
        // redux-mock-storeのセットアップ。ここでモックのStoreを作成する
        const middlewares = [thunk];
        const mockStore = configureStore(middlewares);
        const store = mockStore({});

        //最終的にdispatchを期待するアクション。今回は1つだが複数を想定するため配列に格納
        const expectedActions = [
            { 
                type: actionTypes.AUTH_SUCCESS, 
                token: 'abcdefg',
                uid: 1,
            }
        ]

        // HTTPリクエストを送るエンドポイント
        const url = `http://localhost:3001/api/auth/sign_in`;

        //POST時に渡すデータ
        const authData = {
            email: 'test@example.com',
            password: 'password'
        }

        //帰ってくるレスポンス
        const mockResponse = {
            token: 'abcdefg',
            uid: 1,
        }

        //モックのPOSTリクエストを投げる
        axiosMock.onPost(
            url,
            authData
        ).reply(
          200,
          mockResponse
        );

        // テストするアクションをdispatch
        return store.dispatch(actions.auth(authData))
        .then(() => {
            // 最終的にモックでdispatchされたActionと期待するActionを比較する
            expect(store.getActions()).toEqual(expectedActions);
        }) 
    });
});
```

これでdispatchされたアクションと期待するアクションが一致して、テストが通るはず。

# つまづいたところ
`axiosMock.onPost()`で指定する第二、第三引数の`response`と`headers`は、以下の形式で返ってくる。

```hoge.js
response: {},
headers: {}
```

これに気づかずに、以下のようなオブジェクトを引数に渡していて、dispatchされたアクションのpayloadがundefinedになってしまった。

```hoge.js
const mockResponse = {
    response: {
        token: 'abcdefg',
        uid: 1
    }
}

const mockHeaders = {
    headers: {
        'access-token': 'abcdefg',
        uid: 1
    }
}
```

つまり、上記のようにそれぞれ`response`, `headers`というキーは不要。