21
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-10-16

#概要

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

21
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?