search
LoginSignup
7

More than 5 years have passed since last update.

posted at

React Native - Unit Test part2 (Redux test with Jest)

昨日はUnitテストの設定方法からComponentのユニットテストまで説明しました。今日は、ロジックのテストを解説します。React Nativeでアプリを作る場合は、Fluxアーキテクチャを選ぶと思います。Reduxはその中でも秀逸であり、Testの方法もしっかりドキュメントされています。

アプリのロジックをReduxを用いてテストしていきます。実はここはReact Nativeに限った話ではないかもしれません。しかし、Objective-Cやswiftでしっかりロジックのテストを書くのは難しい(私のswiftスキルがないのが原因だと思いますが。。)ように感じます。個人的にはアプリに状態を持たせて、それをテストできるというReduxの真骨頂がここにあるような気がしています。

Testing store with dispatch

Reduxのドキュメントではactionとreducerを別々にユニットテストしています。同期のactionとreducerはユニットテストできるのですが、非同期のactionのテストが少し難しいです。(例えば、nockがReact Nativeだと動かないなどでハマりました)

そこで私は、Reduxのissue#546で話されている方法を使ってactionとreducerをまとめてテストしています。厳密にはユニットテストではないかもしれませんが、Reduxの挙動をまとめてテストできるので現実的な解としてはアリだと思っています。

下記のactionとreducerをテストします。一般的なGETでJSONを取得する非同期Fetchです。

action

export const FETCH_PLACES_REQUEST = 'FETCH_PLACES_REQUEST';
export const FETCH_PLACES_SUCCESS = 'FETCH_PLACES_SUCCESS';
export const FETCH_PLACES_FAILURE = 'FETCH_PLACES_FAILURE';

function fetchPlacesRequest() {
  return {
    type: FETCH_PLACES_REQUEST
  }
}

function fetchPlacesSuccess(data) {
  return {
    type: FETCH_PLACES_SUCCESS,
    data: data,
  }
}

function fetchPlacesFailure(error) {
  return {
    type: FETCH_PLACES_FAILURE,
    error: error
  }
}

export function fetchPlaces(lat, lng, text) {
  return dispatch => {
    dispatch(fetchPlacesRequest())
    Foursquare.getVenueByLocationAndName(lat, lng, text)
      .then(res => res.json())
      .then(json => dispatch(fetchPlacesSuccess(json.body.results)))
      .catch(error => dispatch(fetchPlacesFailure(error)))
  }
}

reducer

import {
  FETCH_PLACES_REQUEST,
  FETCH_PLACES_SUCCESS,
  FETCH_PLACES_FAILURE
} from './actions';

function places(state = {
  isFetching: false,
  data: [],
  error: null,
}, action){

  switch (action.type) {
    case FETCH_PLACES_REQUEST:
      return Object.assign({}, state, {
        isFetching: true,
      });
    case FETCH_PLACES_SUCCESS:
      return Object.assign({}, state, {
        isFetching: false,
        data: action.data
      });
    case FETCH_PLACES_FAILURE:
      return Object.assign({}, state, {
        isFetching: false,
        error: action.error,
      });
    default:
      return state;
  }
}

Test file

これらをテストするのに下記を用います。storeをlistenしてeventが発動する度にテストを回します。

jest.autoMockOff(); // I don't need to auto mock -- could be a bad knowhow but it works witout stress:)

jest.setMock('../app/utils/Foursquare.js', require('../__mocks__/Foursquare.js')); // issue#335 on facebook/jest

var actions = require('../app/actions');
var configureStore = require('../app/configureStore');
var store = configureStore();

describe('Dispatching places action', () => {
  it(' succeeded when ', (done) => {

    var count = 0;

    var unsubscribe = store.subscribe(function() {
      let state = store.getState();
      console.warn(state)
      if(count === 0 ){
        expect(state.places).toEqual({
          isFetching: true,
          data: [],
          error: null,
        });
      }else if(count === 1){
        expect(state.places).toEqual({
          isFetching: false,
          data: [1,2,3],
          error: null,
        });
      }
      count++;
      if(count > 1){
        done();
      }
   })
   store.dispatch(actions.fetchPlaces(1,2,'foo'));
  })
})

Jestが自動でMockするのがデフォルトの挙動です。しかし、import/exportで記述するとうまく動かなかったりと、ハマりどころがたくさんあるのが私がJest+React Nativeを触った感想です。テストの環境でハマって時間を費やすのはナンセンスなのでjest.autoMockOff();してしまいます。(バッドノウハウだと思います)

Foursquareは下記のようにPromiseでmockしています。

module.exports = {
  getVenueByLocationAndName(lat, lng, name){
    return new Promise((resolve, reject) => {
      resolve({json: this._json });
    });
  },

  _json(){
    return {
      body: {
        results: [1,2,3]
      }
    }
  }
}

JestのデフォルトはJasmineのバージョン1系です。これだとdone()がないので、0.8からJasmineバージョン2に対応したので、package.jsonにtestRunnerの行を追加します。

  "jest": {
    "scriptPreprocessor": "node_modules/react-native/jestSupport/scriptPreprocess.js",
    "setupEnvScriptFile": "node_modules/react-native/jestSupport/env.js",
    "testPathIgnorePatterns": [
      "/node_modules/",
      "packager/react-packager/src/Activity/"
    ],
    "testFileExtensions": [
      "js"
    ],
    "testRunner": "<rootDir>/node_modules/jest-cli/src/testRunners/jasmine/jasmine2.js", // 追加
    "unmockedModulePathPatterns": [
      "promise",
      "react",
      "fbjs",
      "source-map"
    ]
  },

Summary

Jest自体に癖があるし、React Nativeでは動かないもモジュールもあって環境設定に戸惑います。Reduxのテストをまとめてやると効率的だと思うので、今回のような方法を試されてはいかがでしょう(もちろん、普通のReactでも良いと思います)

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
What you can do with signing up
7