web-dev-qa-db-ja.com

このReduxサンクを単体テストする方法は?

だから私はredux thunkミドルウェアを使用しているこのReduxアクションクリエーターを持っています:

accountDetailsActions.js:

export function updateProduct(product) {
  return (dispatch, getState) => {
    const { accountDetails } = getState();

    dispatch({
      type: types.UPDATE_PRODUCT,
      stateOfResidence: accountDetails.stateOfResidence,
      product,
    });
  };
}

どうすればテストできますか?テストにはchaiパッケージを使用しています。オンラインでいくつかのリソースを見つけましたが、どうすればよいかわかりません。これが今までの私のテストです:

accountDetailsReducer.test.js:

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {
        //arrange
        const initialState = {
            product: {}
        };
        const product = {
            id: 1,
            accountTypeId: 1,
            officeRangeId: 1,
            additionalInfo: "",
            enabled: true
        };
        const action = actions.updateProduct(product);
        const store = mockStore({courses: []}, action);
        store.dispatch(action);
        //this is as far as I've gotten - how can I populate my newState variable in order to test the `product` field after running the thunk?
        //act
        const newState = accountDetailsReducer(initialState, action);
        //assert
        expect(newState.product).to.be.an('object');
        expect(newState.product).to.equal(product);
    });
});

私のサンクは非同期アクションを実行しません。何かアドバイス?

12
twilco

Reduxサンクをユニットテストする方法

サンクアクションクリエーターの要点は、将来非同期アクションをディスパッチすることです。 redux-thunkを使用する場合の適切なアプローチは、開始または終了の非同期フローをモデル化して、3つのアクションで成功またはエラーを発生させることです。

この例ではテストにMochaとChaiを使用していますが、アサーションライブラリやテストフレームワークを非常に簡単に使用できます。

メインのサンクアクションクリエーターが管理する複数のアクションを使用した非同期プロセスのモデリング

この例のために、製品を更新する非同期操作を実行し、3つの重要なことを知りたいと仮定します。

  • 非同期操作の開始時
  • 非同期操作が終了したとき
  • 非同期操作が成功したか失敗したか

では、オペレーションのライフサイクルのこれらの段階に基づいて、reuxアクションをモデル化してみましょう。同じことがすべての非同期操作に適用されるので、これは通常、APIからデータをフェッチするためのhttpリクエストに適用されます。

そのように行動を書くことができます。

accountDetailsActions.js:

export function updateProductStarted (product) {
  return {
    type: 'UPDATE_PRODUCT_STARTED',
    product,
    stateOfResidence
  }
}

export function updateProductSuccessful (product, stateOfResidence, timeTaken) {
  return {
    type: 'PRODUCT_UPDATE_SUCCESSFUL',
    product,
    stateOfResidence
    timeTaken
  }
}

export function updateProductFailure (product, err) {
  return {
    product,
    stateOfResidence,
    err
  }
}

// our thunk action creator which dispatches the actions above asynchronously
export function updateProduct(product) {
  return dispatch => {
    const { accountDetails } = getState()
    const stateOfResidence = accountDetails.stateOfResidence

    // dispatch action as the async process has begun
    dispatch(updateProductStarted(product, stateOfResidence))

    return updateUser()
        .then(timeTaken => {
           dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken)) 
        // Yay! dispatch action because it worked
      }
    })
    .catch(error => {
       // if our updateUser function ever rejected - currently never does -
       // oh no! dispatch action because of error
       dispatch(updateProductFailure(product, error))

    })
  }
}

下部の多忙なアクションに注目してください。それがサンクアクションクリエーターです。関数を返すので、redux-thunkミドルウェアによってインターセプトされる特別なアクションです。そのサンクアクションクリエーターは、将来、他のアクションクリエーターを派遣できます。かなり賢い。

これで、ユーザー更新である非同期プロセスをモデル化するアクションを記述しました。このプロセスは、非同期プロセスを処理するための今日最も一般的なアプローチと同様に、promiseを返す関数呼び出しであるとしましょう。

reduxアクションでモデリングしている実際の非同期操作のロジックを定義します

この例では、promiseを返す汎用関数を作成します。これを、ユーザーを更新するか、非同期ロジックを実行する実際の関数に置き換えます。関数がpromiseを返すことを確認してください。

以下で定義する関数を使用して、実用的な自己完結型の例を作成します。実際に動作する例を取得するには、この関数をアクションファイルにスローして、サンクアクションクリエーターのスコープ内にあるようにします。

 // This is only an example to create asynchronism and record time taken
 function updateUser(){
      return new Promise( // Returns a promise will be fulfilled after a random interval
          function(resolve, reject) {
              window.setTimeout(
                  function() {
                      // We fulfill the promise with the time taken to fulfill
                      resolve(thisPromiseCount);
                  }, Math.random() * 2000 + 1000);
          }
      )
})

私たちのテストファイル

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import chai from 'chai' // You can use any testing library
let expect = chai.expect;

import { updateProduct } from './accountDetailsActions.js'

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

describe('Test thunk action creator', () => {
  it('expected actions should be dispatched on successful request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductSuccessful'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })

  it('expected actions should be dispatched on failed request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductFailure'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })
})
12
therewillbecode

公式ドキュメントの レシピ:テストの作成 をご覧ください。また、あなたは何をテストしていますか、アクションクリエーターまたはレデューサー?

アクションクリエーターテストの例

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {    
        const store = mockStore({courses: []});
        const expectedActions = [
            / * your expected actions */
        ];

        return store.dispatch(actions.updateProduct(product))
            .then(() => {
                expect(store.getActions()).to.eql(expectedActions);
            });
    });
});

レジューサーテストの例

レデューサーは純粋な関数である必要があるため、ストア環境の外部で分離してテストできます。

const yourReducer = require('../reducers/your-reducer');

describe('reducer test', () => {
    it('should do things', () => {
        const initialState = {
            product: {}
        };

        const action = {
            type: types.UPDATE_PRODUCT,
            stateOfResidence: // whatever values you want to test with,
            product: {
                id: 1,
                accountTypeId: 1,
                officeRangeId: 1,
                additionalInfo: "",
                enabled: true
            }
        }

        const nextState = yourReducer(initialState, action);

        expect(nextState).to.be.eql({ /* ... */ });
    });
});
6
Mario Tacke
export const someAsyncAction = (param) => (dispatch, getState) => {
    const { mock } = getState();
    dispatch({
        type: 'SOME_TYPE',
        mock: mock + param,
    })
}

it('should test someAsyncAction', () => {
    const param = ' something';
    const dispatch = jest.fn().mockImplementation();
    const getState = () => ({
        mock: 'mock value',
    });

    const expectedAction = {
        type: 'SOME_TYPE',
        mock: 'mock value something'
    };

    const callback = someAsyncAction(param);
    expect(typeof callback).toBe('function');

    callback.call(this, dispatch, getState);
    expect(dispatch.mock.calls[0]).toEqual([expectedAction])
});
0