JavaScript
es6
redux

es6+power-assertでreduxのテストコードを書く

More than 3 years have passed since last update.

追記(2015/10/16)

コメント欄にて @kawazさんにご指摘いただいたpackage.json内のscriptsにおける依存パッケージのコマンド呼び出しを修正しました。

追記(2015/10/10)

reduxにdoc追加のpull requestを送ったところ、mergeされて その後 applyMiddleware()を使う方法のほうが良いってことでtweakされてます。 applyMiddlewareを使う方法はこちら

es6いいですよね。最近reduxを使っており、その関係でes6でのテストコードの書き方も含めざっと調べたいと思ったのでまとめます。

テスト環境のセットアップ

アサーションには、power-assertを使いたいと思います。テストランナーはpower-assert公式でも使っているmochaで。

npmを使ってインストールしていきます。

npm install -D mocha power-assert espower-babel

espower-babel はbabelを用いてes6で書いたテストコードの変換+power-assertを実行するためのコード変換を行うためのライブラリです。便利ですね。

次にpackage.jsonにテストコードを実行するためのscriptを追記していきます。

今回は、test/ ディレクトリ以下にテストコードを書いていきます。

// 省略
"scripts": {
  // 追記
  "test": "mocha --compilers js:espower-babel/guess test/**/*.spec.js",
}

ビルドスクリプトも含めてpackage.jsonに書けばツールごとに使い方を知らなくてよいので重宝してます。あと$(npm bin)を使えばグローバルにインストールしている必要がないのが良いですね。

ここまででテストコードのセットアップは完了です。あとは書いていくだけです。簡単!!!!!

reduxのテストコードを書く

reduxは他のfluxに比べてほとんどの部品が状態を持たないただの関数なので、とてもテストしやすくなっています。

公式にもまとまっており、こちらも大変分かりやすいのですが、非同期なactionのテストに関する記述がなかったのでそちらを例に上げてテストを書いて見たいと思います。

例として

import fetch from 'isomorphic-fetch';
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE
} from '../constants/ActionTypes';

function fetchUsersRequest() {
  return {
    type: FETCH_USERS_REQUEST
  };
}

function fetchUsersSuccess(body) {
  return {
    type: FETCH_USERS_SUCCESS,
    body
  };
}

function fetchUsersFailure(ex) {
  return {
    type: FETCH_USERS_FAILURE,
    ex
  };
}

export function fetchUsers() {
  return dispatch => {
    dispatch(fetchUsersRequest());
    return fetch('http://localhost:3000/users')
      .then(response => response.json())
      .then(json => dispatch(fetchUsersSuccess(json.body)))
      .catch(ex => dispatch(fetchUsersFailure(ex)));
  };
}

こんな感じの非同期なaction creatorを定義したとします。fetchする先はlocalhostになっていますが、APIサーバから取ってくるイメージで

import assert from 'power-assert';
import nock from 'nock'
import * as types from '../../src/constants/ActionTypes';
import * as actions from '../../src/actions/users';

describe('user actions', () => {
  afterEach(() => nock.cleanAll() );

  it('creates FETCH_USERS_SUCCESS when fechting user has been done', (done) => {
    nock('http://localhost:3000/')
      .get('/users')
      .reply(200, { users: {name: 'john'} });

    let expectedActions = [
      { type: types.FETCH_USERS_REQUEST },
      { type: types.FETCH_USERS_SUCCESS, body: { users: {name: 'john'} } }
    ]

    function mockDispatch(action) {
      var expectedAction = expectedActions.shift();
      assert.deepEqual(action, expectedAction);
      if (!expectedActions.length) {
        done();
      }
    }
    actions.fetchUsers()(mockDispatch);
  });

  it('creates FETCH_USERS_FAILURE when fechting user has been failed', (done) => {
    nock('http://localhost:3000/')
      .get('/users')
      .replyWithError('something happened');

    let expectedActions = [
      { type: types.FETCH_USERS_REQUEST },
      { type: types.FETCH_USERS_FAILURE }
    ]

    function mockDispatch(action) {
      var expectedAction = expectedActions.shift();
      assert(action['type'] === expectedAction['type']);
      if (!expectedActions.length) {
        done();
      }
    }
    actions.fetchUsers()(mockDispatch);
  });
});

こんな感じに書けます。

簡単に解説すると、

まずAPIのモックにはnockを使いました。凄く簡単に使えて便利ですね。

次にassertの部分です。非同期なactionの場合、複数のactionが生成されて順番に処理されていきます。そのため1つ目のaction(この場合だとFETCH_USERS_REQUEST)を処理したあと、次のaction(FETCH_USERS_SUCCESSかFETCH_USERS_FAILURE)が処理されます。なので1つのテストで2つのactionが生成されることがテストできれば良さそうです。

  let expectedActions = [
      { type: types.FETCH_USERS_REQUEST },
      { type: types.FETCH_USERS_SUCCESS, body: { users: {name: 'john'} } }
    ]

    function mockDispatch(action) {
      var expectedAction = expectedActions.shift();
      assert.deepEqual(action, expectedAction);
      if (!expectedActions.length) {
        done();
      }
    }
    actions.fetchUsers()(mockDispatch);

なので、こんな感じに書いてテストをしてみました。
こうすればactionの生成される順番もテストできますし、良さそうです。(https://github.com/rackt/redux/issues/546 を凄く参考にしました。。)

redux、まだまだ知見が少ないですが楽しいですね。cycle.jsも気になっているのでそちらも試していこうかなと思います。