axios-mock-adapterで許可したAPIのみmock化する

## 実開発では使いづらい
`axios`には`axios-mock-adapter`という便利なmock化ライブラリがありますが、そのまま使うと全てのリクエストがmock化されてしまうため、APIが並行開発されている現場では使いづらいのが悩みでした。
そんな`axios-mock-adapter`を、指定したAPIのみmock化するように設定していきます。

## 使用するモジュール
・Webpack (3.0.0)
・axios (0.16.2)
https://github.com/mzabriskie/axios
・axios-mock-adapter (1.9.0)
https://github.com/ctimmerm/axios-mock-adapter

## コード

環境変数 `MOCK=true` が設定されている場合のみmock化。
設定されていない場合は `./src/mock/empty.js` を読み込んでmock化しないようにします。


```js:webpack.config.js
const path = require('path');
const webpack = require('webpack');

const isMock = Boolean(process.env.MOCK);

module.exports = {
  ...
  resolve: {
    alias: {
      mock: path.join(__dirname, './src/mock/', isMock ? 'index' : 'empty')
    }
  },
  ...
};
```

```js:package.json
"scripts": {
  "mock": "MOCK=true webpack --progress"
}
```

```js:./src/main.js
import axios from 'axios';
import mock from 'mock';

const axiosInstance = axios.create();
if (mock) {
  mock(axiosInstance);
}
```

```js:./src/mock/index.js
/* eslint no-console: 0 */

import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import responses from './responses';

function match(method, url, response) {
  if (method.toLowerCase() !== response.method.toLowerCase()) {
    return false;
  }
  return url.match(new RegExp(response.url));
}

export default (instance) => {
  let mock = new MockAdapter(instance);

  mock.onAny().reply((config) => {
    const { method, baseURL, url } = config;
    const targetUrl = (baseURL + url).replace(/http\:\/\/.+?\//, '/');
    let then = null;

    for (let i=0; i < responses.length; i++) {
      if (match(method, targetUrl, responses[i])) {
        if (responses[i].then) {
          then = responses[i].then;
          break;
        }
        let res = responses[i].response;
        if (typeof(res) === 'function') {
          res = res();
        }
        const logStyle = 'color: green; font-weight: bold;';

        console.group(`Mock API Request [${new Date().toLocaleString()}]`);

        console.log('%cMethod:', logStyle, method);
        console.log('%cStatus:', logStyle, res.status);
        console.log('%cUrl:', logStyle, targetUrl);
        console.log('%cParams:', logStyle, JSON.stringify(config.params, null, '  '));
        console.log('%cHeaders:', logStyle, JSON.stringify(config.headers, null, '  '));
        console.log('%cData:', logStyle, res.data);

        console.groupEnd();

        // random wait
        return new Promise((resolve) => {
          const rnd = Math.floor(Math.random() * 100) + 1;
          const time = 1000 * (rnd / 100);
          setTimeout(() => {
            resolve([res.status, res.data]);
          }, time);
        });
      }
    }

    // pass through
    return new Promise((resolve) => {
      axios(config)
        .then((res) => {
          if (then) {
            resolve(then(res.status, res.data));
          } else {
            resolve([res.status, res.data]);
          }
        })
        .catch((res) => {
          resolve([res.status, res.data]);
        });
    })
  });
};

```

```js:./src/mock/empty.js
export default null;
```

```js:./src/mock/responses.js
export default responses = [
  {
    method: 'GET', // get|post|put|patch|delete
    url: '^/users', // 正規表現で設定
    response: {
      status: 200,
      data: [
        { id: 1, name: 'hoge' },
        { id: 2, name: 'fuga' }
      ]
    }
  },
  {
    method: 'GET',
    url: '^/users2',
    // responseは関数でも設定可能
    response: () => {
      const isError = window.location.search === '?error=true';
      return {
        status: isError ? 400 : 200,
        data: [
          { id: 1, name: 'hoge' },
          { id: 2, name: 'fuga' }
        ]
      };
    }
  },
  // responseを指定せずthenを指定した場合、実際にリクエストして取得したレスポンスの改変が可能
  {
    method: 'GET',
    url: '^/users3',
    then: (status, data) => {
      data[0].name= 'hogehoge';
      return [status, data];
    }
  },
];
```

## 実行
`$ npm run mock`

```js:axios.get('/users')
status: 200
data: [
  { id: 1, name: 'hoge' },
  { id: 2, name: 'fuga' }
]
```

```js:axios.get('/users2?error=true')
status: 400
data: [
  { id: 1, name: 'hoge' },
  { id: 2, name: 'fuga' }
]
```

```js:axios.get('/users3')
status: 200,
[
  { id: 1, name: 'hogehoge' },
  { id: 2, name: 'fuga' }
]
```