#概要
reduxでactionのテストをしたく、reducerをモックしてactionをテストする
という方針を以下の記事に書いた。
しかし、これだと、asyncな関数の終了タイミングを担保できていないので、別の方針でテストすることにした。
#方針
reduxのドキュメントを参考にする。
asyncなactionは以下のとおりである。
action.js
import {
REQUEST,
REQUEST_SUCCESS,
} from 'constants';
// リソース管理はsuperagent
import request from 'superagent';
function request() {
return {
type: REQUEST,
};
}
function requestSuccess(items) {
return {
type: REQUEST_SUCCESS,
items: items,
};
}
export function fetchSomeResource() {
return (dispatch, getState) => {
dispatch(request());
request
.get('http://apiserver.com/someuri')
.end(function(err, res) {
return dispatch(requestSuccess(res.body.items));
});
};
}
テストは以下のようになった。基本的にはこの記事と同じように実装している。
action.spec.js
import {
REQUEST,
REQUEST_SUCCESS,
} from 'constants';
import { createStoreForActionTest } from 'utils/test';
import { fetchSomeResource } from './action.js';
describe('(Action)', function() {
const initialState = {
items: [],
isFetching: false,
};
const expectedActions = [
{ type: REQUEST },
{ type: REQUEST_SUCCESS, items: ['items'] },
];
it('REQUEST, REQUEST_SUCCESS が disapatch されること', (done) => {
const store = createStoreForActionTest(initialState, expectedActions, done);
store.dispatch(fetchSomeResource());
});
});
utils/test.js
import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
/**
* action のテストに必要な store を作成する。
* dispatch をモックし、内部で、
* 正しく disapatch されているかのテストもあわせて行っている。
*
* @param {Object/Function} getState
* @param {Array} expectedActions
* @param {Function} onLastAction
*/
export function createStoreForActionTest(getState, expectedActions, onLastAction) {
const middleware = [thunk];
if (!Array.isArray(expectedActions)) {
throw new Error('expectedActions should be an array of expected actions.');
}
if (typeof onLastAction !== 'undefined' && typeof onLastAction !== 'function') {
throw new Error('onLastAction should either be undefined or function.');
}
function mockStoreWithoutMiddleware() {
return {
getState() {
return typeof getState === 'function' ?
getState() :
getState;
},
dispatch(action) {
const expectedAction = expectedActions.shift();
// ここが実際のテストコード
expect(action).toEqual(expectedAction);
if (onLastAction && !expectedActions.length) {
onLastAction();
}
return action;
},
};
}
const mockStoreWithMiddleware = applyMiddleware(...middleware)(mockStoreWithoutMiddleware);
return mockStoreWithMiddleware();
}
このやり方だと、reducerすらモックせず、イベントがdispatchされることを内部で検知していて、そこで適当な値かどうかもテストしている。
これにて、asyncな関数であっても問題なくテストができる。
追記
最近見たら、以下の様なライブラリがあって、ここでいうcreateStoreForActionTestの役割をしてくれるよう。