LoginSignup
0
0

More than 5 years have passed since last update.

redux-observableでPromise(axios)使ったりdelayするときのtesting方法

Last updated at Posted at 2016-12-23

問題点

redux-observableのドキュメントにはWritingTestsのページがある。
が、例えばこれがPromiseを返すaxiosとかと組みあわせたりした時ちょっとうまくいかない。

こんなepicを用意して考える。

const axios = require("axios")
const { createEpicMiddleware } = require('redux-observable')
require("rxjs")

const payload = { id: 123 }

// test用にmock化
const mockAdapter = (config) => {
  return new Promise((resolve, reject) => {
    resolve({data: payload, status: 200 })
  })
}

const fakeApi = axios.create({
  adapter: mockAdapter
})

const FETCH_USER = "FETCH_USER"
const FETCH_USER_FULFILLED = "FETCH_USER_FULFILLED"

const fetchUserFulfilled = payload => {
  return { type: FETCH_USER_FULFILLED, payload }
}

const fetchUserEpic = action$ => {
  return action$.ofType(FETCH_USER)
    .mergeMap(action => fakeApi.get(`/api/users/${action.payload}`))
    .map( ({ data }) => fetchUserFulfilled(data) )
}

そして、テストを書いてみる。
これが上記ドキュメントにあるようにそのまま書くと通らない。

const configureMockStore = require('redux-mock-store').default
const epicMiddleware = createEpicMiddleware(fetchUserEpic);
const mockStore = configureMockStore([epicMiddleware]);

describe('fetchUserEpic', () => {
  it('produces the user model', () => {
    // うまくいかないパターン。
    const store = mockStore();
    store.dispatch({ type: FETCH_USER })
    expect(store.getActions()).toEqual([
      { type: FETCH_USER }, // こちらのactonはある
      { type: FETCH_USER_FULFILLED, { id: 123 } } // こちらが無い
    ])
  })
})

これは、下記のようなepicでもtestが通らない事が確認できた

const fetchUserEpic = action$ => {
  return action$.ofType(FETCH_USER)
    .delay(1000)
    .map( () => fetchUserFulfilled(payload) )

つまり、 delayやpromiseで非同期的な動きをする場合にこのままだとうまくいかない

どうするか?

解法1: setTimeoutする

すごい微妙なやりかた。

describe('fetchUserEpic', () => {
  it('produces the user model', (done) => {
    const store = mockStore();
    store.dispatch({ type: FETCH_USER })
    setTimeout( () => {
      expect(store.getActions()).toEqual([
        { type: FETCH_USER }, 
        { type: FETCH_USER_FULFILLED, { id: 123 } }
      ])
      done()
     }, 0)
  })
})

setImmediateとかnextTickでも良いと思う。

解法2: redux-mock-storeのsubscribe機能を使う

もうちょっとスマートに行きたいので、redux-mock-store側の機能を使ってみる。

reduxの本物のstore同様subscribeが用意されているので、これを引っ掛ける

describe('fetchUserEpic', () => {
  it('produces the user model', (done) => {
    const store = mockStore();
    store.dispatch({ type: FETCH_USER })
    const unsubscribe = store.subscribe( () => {
      // ここでassertion
      expect(store.getActions()).toEqual([
        { type: FETCH_USER },
        { type: FETCH_USER_FULFILLED,  { id: 123 } }
      ])
      unsubscribe()
      done()
    })
  })
})

subscribeのタイミングでassertionしている。
今回は「単一のactionが来る事だけを期待したテスト」をしているが、複数のactionが飛んでくることを想定する場合はもうちょっと工夫する必要があるだろう。

更に共通化して扱いたかったらこんな感じにも出来そう

const assertAction = (targetEpic, dispatchAction, expectAction, cb) => {
  const epicMiddleware = createEpicMiddleware(targetEpic);
  const mockStore = configureMockStore([epicMiddleware]);

  const store = mockStore();
  store.dispatch(dispatchAction)
  const unsubscribe = store.subscribe( () => {
    expect(store.getActions()).toEqual(expectAction)
    unsubscribe()
    cb()
  })
}

describe('fetchUserEpic', () => {
  it('produces the user model', (done) => {
    assertAction(fetchUserEpic, { type: FETCH_USER }, [
      { type: FETCH_USER },
      { type: FETCH_USER_FULFILLED,  { id: 123 } }
    ], done)
  })
})
0
0
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
0
0