Edited at

React+Reduxでasyncなactionをテストする

More than 3 years have passed since last update.


概要

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の役割をしてくれるよう。