reduxのテスト

通常のブログ(投稿コメント用のAPIからデータを取得する)の例を使用して、reduxレイヤーをテストでカバーする方法を示します。 ソースはこちらから入手できます







別のアクションとレデューサーの代わりに、アプリケーションでのreduxの開発とテストの両方を大幅に簡素化するducks-patternを使用します。 また、非常に便利なツール-redux-actを使用しますが、createAction()メソッドの説明フィールドに数字、大文字、アンダースコア( 証拠 )のみを追加することが重要です。







まず、タイプ{ type, payload }



単純な「アクションクリエーター」のテストはapp.setLoading()です。







 // src/ducks/app.js import { createAction, createReducer } from 'redux-act' export const REDUCER = 'APP' const NS = `${REDUCER}__` export const initialState = { isLoading: false, } const reducer = createReducer({}, initialState) export const setLoading = createAction(`${NS}SET`) reducer.on(setLoading, (state, isLoading) => ({ ...state, isLoading })) export default reducer
      
      





最初のテスト実行の最小:







 // src/ducks/__tests__/app.test.js import thunk from 'redux-thunk' import configureMockStore from 'redux-mock-store' import { setLoading } from '../app' import reducer from '..' const middlewares = [thunk] const mockStore = configureMockStore(middlewares) describe('sync ducks', () => { it('setLoading()', () => { let state = {} const store = mockStore(() => state) store.dispatch(setLoading(true)) const actions = store.getActions().map(({ type, payload }) => ({ type, payload })) console.log(actions) // ...   -    }) })
      
      





expectedActionsの値をコンソールからコピーします。







  const expectedActions = [{ type: 'APP__SET', payload: true }]; expect(actions).toEqual(expectedActions);
      
      





composeReducers()から取得したルートレデューサーにアクション(各アクションのペイロードにデータを含む)を適用します。







  actions.forEach(action => { state = reducer(state, action) }) expect(state).toEqual({ ...state, app: { ...state.app, isLoading: true }, })
      
      





ストアがコールバック関数mockStore(() => state)



作成されることを明確にする必要がありますmockStore(() => state)



-thunkの副作用内でgetState()



呼び出すときに現在の状態を保証しgetState()



以上で、最初のテストの準備は完了です!







さらに興味深いことに、テストで副作用post.load()をカバーする必要があります。







 // src/ducks/post.js import { createAction, createReducer } from 'redux-act' import { matchPath } from 'react-router' import axios from 'axios' import { load as loadComments } from './comments' export const REDUCER = 'POST' const NS = `${REDUCER}__` export const initialState = {} const reducer = createReducer({}, initialState) const set = createAction(`${NS}SET`) reducer.on(set, (state, post) => ({ ...state, ...post })) export const load = () => (dispatch, getState) => { const state = getState() const match = matchPath(state.router.location.pathname, { path: '/posts/:id' }) const id = match.params.id return axios.get(`/posts/${id}`).then(response => { dispatch(set(response.data)) return dispatch(loadComments(id)) }) } export default reducer
      
      





comments.load()もエクスポートされますが、個別にテストするのはあまり意味がありません。 post.load()内でのみ使用されます。







 // src/ducks/comments.js import { createAction, createReducer } from 'redux-act' import axios from 'axios' export const REDUCER = 'COMMENTS' const NS = `${REDUCER}__` export const initialState = [] const reducer = createReducer({}, initialState) const set = createAction(`${NS}SET`) reducer.on(set, (state, comments) => [...comments]) export const load = postId => dispatch => { return axios.get(`/comments?postId=${postId}`).then(response => { dispatch(set(response.data)) }) } export default reducer
      
      





副作用テスト:







 // src/ducks/__tests__/post.test.js import thunk from 'redux-thunk' import configureMockStore from 'redux-mock-store' import axios from 'axios' import AxiosMockAdapter from 'axios-mock-adapter' import { combineReducers } from 'redux' import post, { load } from '../post' import comments from '../comments' const middlewares = [thunk] const mockStore = configureMockStore(middlewares) const reducerMock = combineReducers({ post, comments, router: (state = {}) => state, }) const axiosMock = new AxiosMockAdapter(axios) describe('sideeffects', () => { afterEach(() => { axiosMock.reset() }) it('load()', () => { const postResponse = { userId: 1, id: 1, title: 'title', body: 'body', } axiosMock.onGet('/posts/1').reply(200, postResponse) const commentsResponse = [ { postId: 1, id: 1, name: 'name', email: 'email@example.com', body: 'body', }, ] axiosMock.onGet('/comments?postId=1').reply(200, commentsResponse) let state = { router: { location: { pathname: '/posts/1', }, }, } const store = mockStore(() => state) return store.dispatch(load()).then(() => { const actions = store.getActions().map(({ type, payload }) => ({ type, payload })) const expectedActions = [ { type: 'POST__SET', payload: postResponse, }, { type: 'COMMENTS__SET', payload: commentsResponse }, ] actions.forEach(action => { state = reducerMock(state, action) }) expect(state).toEqual({ ...state, post: { ...state.post, ...postResponse }, comments: [...commentsResponse], }) }) }) })
      
      





より良い方法はわかりませんが、ルーターレデューサーを初期化するために、reducerMockでルートレデューサーを再構築する必要がありました。 さらに、2つのaxiosリクエストに対応します。 store.dispatch()にさらに戻り値が追加されました。 Promiseに包まれた; しかし、代替があります-done()コールバック関数:







  it('', done => { setTimeout(() => { //... done() }, 1000) }
      
      





それ以外の場合、副作用のテストは、単純な「アクション作成者」のテストほど複雑ではありません。 ソースはこちらから入手できます








All Articles