如何对Redux thunk进行单元测试?

dsekswqp  于 2023-10-19  发布在  其他
关注(0)|答案(4)|浏览(116)

所以我有一个使用redux thunk中间件的Redux action creator:

accountDetailsActions.js:

  1. export function updateProduct(product) {
  2. return (dispatch, getState) => {
  3. const { accountDetails } = getState();
  4. dispatch({
  5. type: types.UPDATE_PRODUCT,
  6. stateOfResidence: accountDetails.stateOfResidence,
  7. product,
  8. });
  9. };
  10. }

我该如何测试它?我正在使用chai包进行测试。我在网上找到了一些资源,但不确定如何继续。以下是我到目前为止的测试:

accountDetailsReducer.test.js:

  1. describe('types.UPDATE_PRODUCT', () => {
  2. it('should update product when passed a product object', () => {
  3. //arrange
  4. const initialState = {
  5. product: {}
  6. };
  7. const product = {
  8. id: 1,
  9. accountTypeId: 1,
  10. officeRangeId: 1,
  11. additionalInfo: "",
  12. enabled: true
  13. };
  14. const action = actions.updateProduct(product);
  15. const store = mockStore({courses: []}, action);
  16. store.dispatch(action);
  17. //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?
  18. //act
  19. const newState = accountDetailsReducer(initialState, action);
  20. //assert
  21. expect(newState.product).to.be.an('object');
  22. expect(newState.product).to.equal(product);
  23. });
  24. });

我的形实转换程序不执行任何异步操作。有什么建议吗

jgzswidk

jgzswidk1#

如何对Redux Thunks进行单元测试

形实转换操作创建器的全部意义在于在将来分派异步操作。在使用redux-thunk时,一个好的方法是用三个动作来模拟开始和结束的过程,从而导致成功或错误。
虽然这个例子使用Mocha和Chai进行测试,但您可以很容易地使用任何Assert库或测试框架。

使用由我们的主形实转换操作创建器管理的多个操作来建模CMAC进程

在这个例子中,我们假设您想要执行一个更新产品的异步操作,并且想要知道三个关键的事情。

  • 当开始执行cdc操作时,
  • 当dec操作完成时
  • blog操作是成功还是失败

好了,现在是时候根据操作生命周期的这些阶段来对redux操作进行建模了。请记住,这同样适用于所有的javascript操作,因此这通常适用于从API获取数据的http请求。
我们可以这样写我们的行为。

  • accountDetailsActions.js:*
  1. export function updateProductStarted (product) {
  2. return {
  3. type: 'UPDATE_PRODUCT_STARTED',
  4. product,
  5. stateOfResidence
  6. }
  7. }
  8. export function updateProductSuccessful (product, stateOfResidence, timeTaken) {
  9. return {
  10. type: 'PRODUCT_UPDATE_SUCCESSFUL',
  11. product,
  12. stateOfResidence
  13. timeTaken
  14. }
  15. }
  16. export function updateProductFailure (product, err) {
  17. return {
  18. product,
  19. stateOfResidence,
  20. err
  21. }
  22. }
  23. // our thunk action creator which dispatches the actions above asynchronously
  24. export function updateProduct(product) {
  25. return dispatch => {
  26. const { accountDetails } = getState()
  27. const stateOfResidence = accountDetails.stateOfResidence
  28. // dispatch action as the async process has begun
  29. dispatch(updateProductStarted(product, stateOfResidence))
  30. return updateUser()
  31. .then(timeTaken => {
  32. dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken))
  33. // Yay! dispatch action because it worked
  34. }
  35. })
  36. .catch(error => {
  37. // if our updateUser function ever rejected - currently never does -
  38. // oh no! dispatch action because of error
  39. dispatch(updateProductFailure(product, error))
  40. })
  41. }
  42. }

注意底部的忙碌看起来很忙的动作。他就是我们的思维活动创造者。因为它返回一个函数,所以它是一个被redux-thunk中间件拦截的特殊操作。该形实转换操作创建者可以在将来的某个时间点分派其他操作创建者。相当聪明。
现在,我们已经编写了操作来建模一个异步流程,这是一个用户更新。假设这个进程是一个函数调用,它返回一个promise,这将是目前处理BRAC进程的最常见方法。

为我们正在使用redux操作建模的实际BRAC操作定义逻辑

在这个例子中,我们将创建一个返回promise的泛型函数。将其替换为更新用户的实际函数或执行DMAC逻辑。确保函数返回一个promise。
我们将使用下面定义的函数来创建一个可工作的自包含示例。要获得一个工作示例,只需将此函数放入actions文件中,使其处于thunk action creator的作用域中。

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

我们的测试文件

  1. import configureMockStore from 'redux-mock-store'
  2. import thunk from 'redux-thunk'
  3. import chai from 'chai' // You can use any testing library
  4. let expect = chai.expect;
  5. import { updateProduct } from './accountDetailsActions.js'
  6. const middlewares = [ thunk ]
  7. const mockStore = configureMockStore(middlewares)
  8. describe('Test thunk action creator', () => {
  9. it('expected actions should be dispatched on successful request', () => {
  10. const store = mockStore({})
  11. const expectedActions = [
  12. 'updateProductStarted',
  13. 'updateProductSuccessful'
  14. ]
  15. return store.dispatch(fetchSomething())
  16. .then(() => {
  17. const actualActions = store.getActions().map(action => action.type)
  18. expect(actualActions).to.eql(expectedActions)
  19. })
  20. })
  21. it('expected actions should be dispatched on failed request', () => {
  22. const store = mockStore({})
  23. const expectedActions = [
  24. 'updateProductStarted',
  25. 'updateProductFailure'
  26. ]
  27. return store.dispatch(fetchSomething())
  28. .then(() => {
  29. const actualActions = store.getActions().map(action => action.type)
  30. expect(actualActions).to.eql(expectedActions)
  31. })
  32. })
  33. })
展开查看全部
crcmnpdw

crcmnpdw2#

看看食谱:根据官方文档编写测试。还有,你在测试什么,动作创建器还是缩减器?

Action Creator测试示例

  1. describe('types.UPDATE_PRODUCT', () => {
  2. it('should update product when passed a product object', () => {
  3. const store = mockStore({courses: []});
  4. const expectedActions = [
  5. / * your expected actions */
  6. ];
  7. return store.dispatch(actions.updateProduct(product))
  8. .then(() => {
  9. expect(store.getActions()).to.eql(expectedActions);
  10. });
  11. });
  12. });

减速器测试示例

你的reducer应该是一个纯函数,这样你就可以在商店环境之外单独测试它。

  1. const yourReducer = require('../reducers/your-reducer');
  2. describe('reducer test', () => {
  3. it('should do things', () => {
  4. const initialState = {
  5. product: {}
  6. };
  7. const action = {
  8. type: types.UPDATE_PRODUCT,
  9. stateOfResidence: // whatever values you want to test with,
  10. product: {
  11. id: 1,
  12. accountTypeId: 1,
  13. officeRangeId: 1,
  14. additionalInfo: "",
  15. enabled: true
  16. }
  17. }
  18. const nextState = yourReducer(initialState, action);
  19. expect(nextState).to.be.eql({ /* ... */ });
  20. });
  21. });
展开查看全部
1szpjjfi

1szpjjfi3#

  1. export const someAsyncAction = (param) => (dispatch, getState) => {
  2. const { mock } = getState();
  3. dispatch({
  4. type: 'SOME_TYPE',
  5. mock: mock + param,
  6. })
  7. }
  8. it('should test someAsyncAction', () => {
  9. const param = ' something';
  10. const dispatch = jest.fn().mockImplementation();
  11. const getState = () => ({
  12. mock: 'mock value',
  13. });
  14. const expectedAction = {
  15. type: 'SOME_TYPE',
  16. mock: 'mock value something'
  17. };
  18. const callback = someAsyncAction(param);
  19. expect(typeof callback).toBe('function');
  20. callback.call(this, dispatch, getState);
  21. expect(dispatch.mock.calls[0]).toEqual([expectedAction])
  22. });
展开查看全部
byqmnocz

byqmnocz4#

  1. import {createStore, applyMiddleWare, combineReducers} from 'redux';
  2. import contactsReducer from '../reducers/contactsReducer';
  3. function getThunk(dispatchMock){
  4. return ({dispatch, getState}) => (next) => (action) => {
  5. if(typeof action === 'function'){
  6. // dispatch mock function whenever we are dispatching thunk action
  7. action(dispatchMock, getState);
  8. return action(dispatch, getState);
  9. }
  10. // dispatch mock function whenever we are dispatching action
  11. dispatchMock(action);
  12. return next(action);
  13. }
  14. }
  15. describe('Test Redux reducer with Thunk Actions', ()=>{
  16. test('should add contact on dispatch of addContact action',()=>{
  17. // to track all actions dispatched from store, creating mock.
  18. const storeDispatchMock = jest.fn();
  19. const reducer = combineReducers({
  20. Contacts: contactsReducer
  21. })
  22. const store = createStore( reducer, {Contacts:[]}, applyMiddleware(getThunk(storeDispatchMock));
  23. store.dispatch(someThunkAction({name:"test1"}))
  24. expect(storeDispatchMock).toHaveBeenCalledWith({
  25. type:'contactUpdate', payload:{{name:"test1"}}
  26. })
  27. expect(store.getState()).toBe({Contacts:[{name:"test1"}]})
  28. })
  29. })
展开查看全部

相关问题