web-dev-qa-db-ja.com

Jestを使用してReduxサンクでディスパッチされたアクションをテストする

私はJestが初めてで、明らかに非同期コードのテストの専門家ではありません...

私が使用する単純なFetchヘルパーがあります。

_export function fetchHelper(url, opts) {
    return fetch(url, options)
        .then((response) => {
            if (response.ok) {
                return Promise.resolve(response);
            }

            const error = new Error(response.statusText || response.status);
            error.response = response;

            return Promise.reject(error);
        });
    }
_

そして、次のように実装します。

_export function getSomeData() {
    return (dispatch) => {
        return fetchHelper('http://datasource.com/').then((res) => {
            dispatch(setLoading(true));
            return res.json();
        }).then((data) => {
            dispatch(setData(data));
            dispatch(setLoading(false));
        }).catch(() => {
            dispatch(setFail());
            dispatch(setLoading(false));
        });
    };
}
_

ただし、正しい状況で正しい順序で正しいディスパッチが起動されることをテストしたいと思います。

これはsinon.spy()を使用すると非常に簡単でしたが、Jestでこれを複製する方法はわかりません。理想的には、テストが次のようになります。

_expect(spy.args[0][0]).toBe({
  type: SET_LOADING_STATE,
  value: true,
});


expect(spy.args[1][0]).toBe({
  type: SET_DATA,
  value: {...},
});
_

ヘルプやアドバイスを事前に感謝します!

10
DanV

Reduxのドキュメントには素晴らしい 非同期アクション作成者のテストに関する記事 :があります。

Redux Thunk または他のミドルウェアを使用する非同期アクションクリエーターの場合、テストのためにReduxストアを完全にモックすることが最善です。 redux-mock-store を使用して、ミドルウェアを模擬ストアに適用できます。 fetch-mock を使用してHTTPリクエストをモックすることもできます。

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)

describe('async actions', () => {
  afterEach(() => {
    fetchMock.reset()
    fetchMock.restore()
  })

  it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
    fetchMock
      .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } })


    const expectedActions = [
      { type: types.FETCH_TODOS_REQUEST },
      { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
    ]
    const store = mockStore({ todos: [] })

    return store.dispatch(actions.fetchTodos()).then(() => {
      // return of async actions
      expect(store.getActions()).toEqual(expectedActions)
    })
  })
})

彼らのアプローチは、jest(またはsinon)を使用してスパイするのではなく、模擬ストアを使用して、ディスパッチされたアクションをアサートすることです。これには、サンクをディスパッチするサンクを処理できるという利点がありますが、これはスパイでは非常に困難です。

これはすべてドキュメントから直接ですが、サンクのサンプルを作成してほしい場合はお知らせください。

9
Michael Peyper

Redux Thunkまたは他のミドルウェアを使用する非同期アクションクリエーターの場合、テストのためにReduxストアを完全にモックするのが最善です。 _redux-mock-store_を使用して、ミドルウェアを模擬ストアに適用できます。 HTTPリクエストをモックするには、nockを使用できます。

_redux-mock-store_ documentation によると、非同期アクションをテストするには、リクエストの最後でstore.getActions()を呼び出す必要があります。テストは次のように設定できます。

mockStore(getState?: Object,Function) => store: Function構成済みの模擬ストアのインスタンスを返します。テストのたびにストアをリセットする場合は、この関数を呼び出す必要があります。

store.dispatch(action) => actionモックストアを介してアクションをディスパッチします。アクションはインスタンス内の配列に保存され、実行されます。

store.getState() => state: Objectモックストアの状態を返します

store.getActions() => actions: Arrayモックストアのアクションを返します

store.clearActions()保存されているアクションをクリアします

次のようなテストアクションを記述できます。

_import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';

test('test getSomeData', () => {
    const store = mockStore({});

    nock('http://datasource.com/', {
       reqheaders // you can optionally pass the headers here
    }).reply(200, yourMockResponseHere);

    const expectedActions = [
        setLoading(true),
        setData(yourMockResponseHere),
        setLoading(false)
    ];

    const dispatchedStore = store.dispatch(
        getSomeData()
    );
    return dispatchedStore.then(() => {
        expect(store.getActions()).toEqual(expectedActions);
    });
});
_

PSモックされたアクションが実行されたとき、および前の更新後のデータに依存している場合、モックストアはそれ自体を更新しないことに注意してください次のアクションで使用するアクションは、次のように独自のインスタンスを記述する必要があります

_const getMockStore = (actions) => {
    //action returns the sequence of actions fired and 
    // hence you can return the store values based the action
    if(typeof action[0] === 'undefined') {
         return {
             reducer: {isLoading: true}
         }
    } else {
        // loop over the actions here and implement what you need just like reducer

    }
}
_

そしてmockStoreを次のように設定します

_ const store = mockStore(getMockStore);
_

それが役に立てば幸い。また、非同期アクション作成者のテストに関するreduxドキュメントのthisも確認してください

6
Shubham Khatri

ディスパッチ関数をjest.fn()でモックしている場合は、dispatch.mock.callsスタブに対して行われたすべての呼び出しを取得します。

  const dispatch = jest.fn();
  actions.yourAction()(dispatch);

  expect(dispatch.mock.calls.length).toBe(1);

  expect(dispatch.mock.calls[0]).toBe({
    type: SET_DATA,
    value: {...},
  });
2
Canastro

私の答えでは、axiosの代わりにfetchを使用しています。フェッチの約束に関する経験があまりないので、あなたの質問には関係ありません。個人的にはaxiosに非常に満足しています。
以下で提供しているコードサンプルをご覧ください。

// apiCalls.js
const fetchHelper = (url) => {
  return axios.get(url);
}


import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
  it('should dispatch SET_LOADING_STATE on start of call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_LOADING_STATE,
      value: true,
    });
  });

  it('should dispatch SET_DATA action on successful api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_DATA,
      value: { ...},
    });
  });

  it('should dispatch SET_FAIL action on failed api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_FAIL,
    });
  });
});

ここでは、成功部分をテストするResolved promiseを返し、失敗したapi呼び出しをテストするpromiseを拒否するようにフェッチヘルパーをモックしています。引数を引数に渡して、応答時に検証することもできます。
次のようにgetSomeDataを実装できます。

const getSomeData = () => {
  return (dispatch) => {
    dispatch(setLoading(true));
    return fetchHelper('http://datasource.com/')
      .then(response => {
        dispatch(setData(response.data));
        dispatch(setLoading(false));
      })
      .catch(error => {
        dispatch(setFail());
        dispatch(setLoading(false));
      })
  }
}

これで問題が解決することを願っています。説明が必要な場合はコメントしてください。
P.S上記のコードを見ると、なぜ私がフェッチよりもaxiosを好むのかがわかり、多くの約束の解決からあなたを救います!
詳細については、以下を参照してください。 https://medium.com/@thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5

0
Swapnil