繁体   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