実開発では使いづらい
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化しないようにします。
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')
}
},
...
};
package.json
"scripts": {
"mock": "MOCK=true webpack --progress"
}
./src/main.js
import axios from 'axios';
import mock from 'mock';
const axiosInstance = axios.create();
if (mock) {
mock(axiosInstance);
}
./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]);
});
})
});
};
./src/mock/empty.js
export default null;
./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
axios.get('/users')
status: 200
data: [
{ id: 1, name: 'hoge' },
{ id: 2, name: 'fuga' }
]
axios.get('/users2?error=true')
status: 400
data: [
{ id: 1, name: 'hoge' },
{ id: 2, name: 'fuga' }
]
axios.get('/users3')
status: 200,
[
{ id: 1, name: 'hogehoge' },
{ id: 2, name: 'fuga' }
]