簡體   English   中英

如何對這個Redux thunk進行單元測試?

[英]How to unit test this Redux thunk?

所以我有這個Redux動作創建者正在使用redux thunk中間件:

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);
    });
});

我的thunk不做任何異步操作。 有什么建議?

如何對Redux Thunks進行單元測試

thunk動作創建者的重點是將來調度異步動作。 當使用redux-thunk時,一個好的方法是建模開始和結束的異步流,導致成功或三個動作的錯誤。

雖然這個例子使用Mocha和Chai進行測試,但你可以很容易地使用任何斷言庫或測試框架。

使用由我們的主thunk動作創建者管理的多個動作對異步過程建模

讓我們假設您希望執行一個更新產品並想知道三個關鍵事項的異步操作。

  • 異步操作開始時
  • 異步操作完成時
  • 異步操作是成功還是失敗

好的時候根據操作生命周期的這些階段來建模我們的redux動作。 請記住,這同樣適用於所有異步操作,因此這通常應用於http請求以從api獲取數據。

我們可以這樣寫我們的行為。

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))

    })
  }
}

請注意底部的忙碌動作。 那是我們的thunk行動創造者。 由於它返回一個函數,因此它是一個由redux-thunk中間件攔截的特殊操作。 thunk動作創建者可以在將來的某個時刻派遣其他動作創建者。 很聰明。

現在我們已經編寫了動作來模擬異步過程,這是一個用戶更新。 假設這個過程是一個函數調用,它返回一個promise,這是當今處理異步進程的最常用方法。

為我們使用redux操作建模的實際異步操作定義邏輯

對於此示例,我們將只創建一個返回promise的泛型函數。 將其替換為更新用戶或執行異步邏輯的實際功能。 確保該函數返回一個promise。

我們將使用下面定義的函數來創建一個工作自包含的示例。 要獲得一個有用的示例,只需將此函數放入您的操作文件中,以便它位於您的thunk動作創建者的范圍內。

 // 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)
     })

  })
})

從官方文檔中查看Recipe:Writing Tests 另外,你在測試什么,動作創建者還是減速器?

動作創建者測試示例

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);
            });
    });
});

減速機測試實例

您的reducer應該是一個純函數,因此您可以在商店環境之外單獨測試它。

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({ /* ... */ });
    });
});
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])
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM