如何对Reduxsauce进行单元测试? [英] How to unit test reduxsauce?

查看:147
本文介绍了如何对Reduxsauce进行单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将reduxsauce库用于redux存储,并且我想对其中的单个redux存储进行单元测试. redux文件:

 import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'

/* ------------- Types and Action Creators ------------- */

const { Types, Creators } = createActions({
  getLanguage: [],
  setLanguage: ['language']
})

export const LanguageTypes = Types
export default Creators

/* ------------- Initial State ------------- */

export const INITIAL_STATE = Immutable({
  language: "en"
})

/* ------------- Reducers ------------- */


export const getLanguage = (state: Object, {}: Object) => {
    return state.merge({})
}

export const setLanguage = (state: Object, { language }: Object) => {
    return state.merge({ language })
}

/* ------------- Hookup Reducers To Types ------------- */

export const reducer = createReducer(INITIAL_STATE, {
  [Types.SET_LANGUAGE]: setLanguage,
  [Types.GET_LANGUAGE]: getLanguage,
})
 

测试:

 import * as actions from '../../../redux/LanguageRedux'
import * as types from '../../../redux/LanguageRedux'

describe('Language redux ', () => {
  it('should have default language ', () => {
    expect(actions.INITIAL_STATE.language).toEqual("en")
  }),
  it('should be able to set the language', () => {
    // I know the calls below are not tests but still its relevant with the error
    actions.getLanguage()
    actions.setLanguage()
  })
})
 

错误:

 ● Language redux  › should be able to set the language

    TypeError: Cannot destructure 'undefined' or 'null'.

      21 |
      22 |
    > 23 | export const getLanguage = (state: Object, {}: Object) => {
         |                            ^
      24 |     return state.merge({})
      25 | }
      26 |

      at Object.getLanguage (src/redux/LanguageRedux.js:23:28)
      at Object.getLanguage (src/__tests__/src/redux/LanguageRedux.js:9:13)
 

现在,我在另一个文件中配置了存储,但是reducer合并在另一个文件中:

 import { combineReducers } from 'redux'
import configureStore from './CreateStore'

import rootSaga from '../sagas'

export default () => {
    /* ------------- Assemble The Reducers ------------- */
    const rootReducer = combineReducers({
        language: require('./LanguageRedux').reducer
    })

    return configureStore(rootReducer, rootSaga)
}
 

任何人都有关于如何测试redux动作等的线索.使用正常的redux我可以找到很多文章,但是使用reduxsauce库,我似乎什么也找不到.有什么线索吗?

解决方案

正在测试什么

LanguageRedux.js具有以下导出:

  • LanguageTypes-动作类型的映射
  • Creators-动作创建者的地图
  • INITIAL_STATE-应用的初始状态
  • getLanguagesetLanguage-减速器功能
  • reducer-redux减速器

我建议导入具有预期标识符的所有内容,如下所示:

 import Creators, {
  LanguageTypes,
  INITIAL_STATE,
  getLanguage,
  setLanguage,
  reducer
 } from '../../../redux/LanguageRedux';
 

注意:看来getLanguage动作是不必要的,因为它对状态没有任何作用(如果应用获取的语言应仅从状态中读取),但是我它将保留在这里,因为它在问题代码中.


语言类型

LanguageTypes只是动作类型与其关联的字符串值的映射:

 it('should export the expected action types', () => {
  expect(LanguageTypes).toEqual({
    GET_LANGUAGE: 'GET_LANGUAGE',
    SET_LANGUAGE: 'SET_LANGUAGE'
  });  // Success!
});
 


创作者

Creators是动作创建者的地图.

每个动作创建者都是一个纯函数,可以根据给定的参数生成一个动作对象:

 describe('Creators', () => {

  describe('getLanguage', () => {

    it('should return the expected action', () => {
      expect(Creators.getLanguage()).toEqual({
        type: LanguageTypes.GET_LANGUAGE
      });
    });

    it('should ignore extra args', () => {
      expect(Creators.getLanguage('extra arg')).toEqual({
        type: LanguageTypes.GET_LANGUAGE
      });
    });

  });

  describe('setLanguage', () => {

    it('should return the expected action when passed nothing', () => {
      expect(Creators.setLanguage()).toEqual({
        type: LanguageTypes.SET_LANGUAGE
      });  // Success!
    });

    it('should return the expected action when passed a language', () => {
      expect(Creators.setLanguage('en')).toEqual({
        type: LanguageTypes.SET_LANGUAGE,
        language: 'en'
      });  // Success!
    });

    it('should ignore extra args', () => {
      expect(Creators.setLanguage('es', 'extra arg')).toEqual({
        type: LanguageTypes.SET_LANGUAGE,
        language: 'es'
      });  // Success!
    });

  });

});
 


INITIAL_STATE

INITIAL_STATE仅仅是应用程序开始的初始状态对象:

 it('should set the initial state ', () => {
  expect(INITIAL_STATE).toEqual({ language: "en" });  // Success!
});
 


减速器功能

getLanguagesetLanguage是化简函数,这意味着它们是纯函数,它们根据给定的现有状态和操作返回新状态:

 describe('reducers', () => {

  describe('getLanguage', () => {

    it('should do nothing (probably should not be an action)', () => {
      expect(getLanguage(INITIAL_STATE, {})).toEqual(INITIAL_STATE);  // Success!
    });

    it('should ignore extra args', () => {
      expect(getLanguage(INITIAL_STATE, { extra: 'arg' })).toEqual(INITIAL_STATE);  // Success!
    });

  });

  describe('setLanguage', () => {

    it('should set the language', () => {
      expect(setLanguage(INITIAL_STATE, { language: 'es' })).toEqual({
        language: 'es'
      });  // Success!
    });

    it('should ignore extra args', () => {
      expect(setLanguage(INITIAL_STATE, { language: 'fr', extra: 'arg' })).toEqual({
        language: 'fr'
      });  // Success!
    });

  });

});
 

请注意,使用reduxsauce测试化简器功能甚至比测试标准redux化简器更容易,因为它们将仅被调用以执行其设计要处理的动作.


减速器

reducer是redux reducer,其工作是将动作路由到相应的reducer函数并返回结果状态:

 describe('reducer', () => {

  it('should return initial state if passed nothing', () => {
    expect(reducer()).toEqual(INITIAL_STATE);  // Success!
  });

  it('should route GET_LANGUAGE to getLanguage', () => {
    expect(reducer(INITIAL_STATE, Creators.getLanguage())).toEqual(INITIAL_STATE);  // Success!
  });

  it('should route SET_LANGUAGE to setLanguage', () => {
    expect(reducer(Immutable({ language: 'es' }), Creators.setLanguage('fr'))).toEqual({
      language: 'fr'
    });  // Success!
  });

});
 

注意:可以使用几种不同的方法来测试reducer.上面的方法通过化简器功能一直传递状态和动作.它很彻底,但是与上面的reducer功能测试有很多重叠.

最基本的选择是监视createReducer并只需验证是否已使用预期的INITIAL_STATE和映射对象调用了它.

与上述完整方法中间的方法是模拟化简器功能,传递reducer各种动作,并验证是否调用了正确的化简器功能.这可能是理想的方法,但是由于createReducer会在导入代码并捕获对本地函数setLanguage和<的引用后立即运行,因此难以实现当前编写代码的方式. c8>.如果您想使用这种方法,那么最简单的方法是将reducer移至其自己的模块,以便可以在将reducer导入测试代码之前模拟 的reducer函数. /p>

I am using reduxsauce library for redux store, and I want to unit test a single redux store in it. The redux file:

import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'

/* ------------- Types and Action Creators ------------- */

const { Types, Creators } = createActions({
  getLanguage: [],
  setLanguage: ['language']
})

export const LanguageTypes = Types
export default Creators

/* ------------- Initial State ------------- */

export const INITIAL_STATE = Immutable({
  language: "en"
})

/* ------------- Reducers ------------- */


export const getLanguage = (state: Object, {}: Object) => {
    return state.merge({})
}

export const setLanguage = (state: Object, { language }: Object) => {
    return state.merge({ language })
}

/* ------------- Hookup Reducers To Types ------------- */

export const reducer = createReducer(INITIAL_STATE, {
  [Types.SET_LANGUAGE]: setLanguage,
  [Types.GET_LANGUAGE]: getLanguage,
})

The test:

import * as actions from '../../../redux/LanguageRedux'
import * as types from '../../../redux/LanguageRedux'

describe('Language redux ', () => {
  it('should have default language ', () => {
    expect(actions.INITIAL_STATE.language).toEqual("en")
  }),
  it('should be able to set the language', () => {
    // I know the calls below are not tests but still its relevant with the error
    actions.getLanguage()
    actions.setLanguage()
  })
})

Error:

● Language redux  › should be able to set the language

    TypeError: Cannot destructure 'undefined' or 'null'.

      21 |
      22 |
    > 23 | export const getLanguage = (state: Object, {}: Object) => {
         |                            ^
      24 |     return state.merge({})
      25 | }
      26 |

      at Object.getLanguage (src/redux/LanguageRedux.js:23:28)
      at Object.getLanguage (src/__tests__/src/redux/LanguageRedux.js:9:13)

Now, I have the store configured in a different file but reducers is combined in a different file:

import { combineReducers } from 'redux'
import configureStore from './CreateStore'

import rootSaga from '../sagas'

export default () => {
    /* ------------- Assemble The Reducers ------------- */
    const rootReducer = combineReducers({
        language: require('./LanguageRedux').reducer
    })

    return configureStore(rootReducer, rootSaga)
}

Any one has a clue as to how could I test the redux actions etc. With normal redux I could find many articles but with reduxsauce library I cant seem to find anything. Any clues please?

解决方案

What is being tested

LanguageRedux.js has the following exports:

  • LanguageTypes - a map of the action types
  • Creators - a map of the action creators
  • INITIAL_STATE - the initial state of the app
  • getLanguage and setLanguage - the reducer functions
  • reducer - the redux reducer

I recommend importing everything with the expected identifiers like this:

import Creators, {
  LanguageTypes,
  INITIAL_STATE,
  getLanguage,
  setLanguage,
  reducer
 } from '../../../redux/LanguageRedux';

Note: It looks like the getLanguage action is unnecessary since it does nothing to the state (if the app is getting the language it should just read it from the state), but I'll leave it in there since it is in the question code.


LanguageTypes

LanguageTypes is just a map of the action types to their associated string value:

it('should export the expected action types', () => {
  expect(LanguageTypes).toEqual({
    GET_LANGUAGE: 'GET_LANGUAGE',
    SET_LANGUAGE: 'SET_LANGUAGE'
  });  // Success!
});


Creators

Creators is a map of the action creators.

Each action creator is a pure function that generates an action object based on the parameters given:

describe('Creators', () => {

  describe('getLanguage', () => {

    it('should return the expected action', () => {
      expect(Creators.getLanguage()).toEqual({
        type: LanguageTypes.GET_LANGUAGE
      });
    });

    it('should ignore extra args', () => {
      expect(Creators.getLanguage('extra arg')).toEqual({
        type: LanguageTypes.GET_LANGUAGE
      });
    });

  });

  describe('setLanguage', () => {

    it('should return the expected action when passed nothing', () => {
      expect(Creators.setLanguage()).toEqual({
        type: LanguageTypes.SET_LANGUAGE
      });  // Success!
    });

    it('should return the expected action when passed a language', () => {
      expect(Creators.setLanguage('en')).toEqual({
        type: LanguageTypes.SET_LANGUAGE,
        language: 'en'
      });  // Success!
    });

    it('should ignore extra args', () => {
      expect(Creators.setLanguage('es', 'extra arg')).toEqual({
        type: LanguageTypes.SET_LANGUAGE,
        language: 'es'
      });  // Success!
    });

  });

});


INITIAL_STATE

INITIAL_STATE is simply the initial state object the app starts with:

it('should set the initial state ', () => {
  expect(INITIAL_STATE).toEqual({ language: "en" });  // Success!
});


Reducer functions

getLanguage and setLanguage are reducer functions, meaning they are pure functions that return a new state based on the existing state and action they are given:

describe('reducers', () => {

  describe('getLanguage', () => {

    it('should do nothing (probably should not be an action)', () => {
      expect(getLanguage(INITIAL_STATE, {})).toEqual(INITIAL_STATE);  // Success!
    });

    it('should ignore extra args', () => {
      expect(getLanguage(INITIAL_STATE, { extra: 'arg' })).toEqual(INITIAL_STATE);  // Success!
    });

  });

  describe('setLanguage', () => {

    it('should set the language', () => {
      expect(setLanguage(INITIAL_STATE, { language: 'es' })).toEqual({
        language: 'es'
      });  // Success!
    });

    it('should ignore extra args', () => {
      expect(setLanguage(INITIAL_STATE, { language: 'fr', extra: 'arg' })).toEqual({
        language: 'fr'
      });  // Success!
    });

  });

});

Note that testing reducer functions with reduxsauce is even easier than testing standard redux reducers since they will only be called for actions they are designed to handle.


reducer

reducer is the redux reducer and its job is to route actions to the corresponding reducer function and return the resulting state:

describe('reducer', () => {

  it('should return initial state if passed nothing', () => {
    expect(reducer()).toEqual(INITIAL_STATE);  // Success!
  });

  it('should route GET_LANGUAGE to getLanguage', () => {
    expect(reducer(INITIAL_STATE, Creators.getLanguage())).toEqual(INITIAL_STATE);  // Success!
  });

  it('should route SET_LANGUAGE to setLanguage', () => {
    expect(reducer(Immutable({ language: 'es' }), Creators.setLanguage('fr'))).toEqual({
      language: 'fr'
    });  // Success!
  });

});

Note: there are a few different ways that reducer can be tested. The above approach passes the state and actions all the way through the reducer functions. It is thorough, but also has a lot of overlap with the reducer function tests above.

The most basic alternative is to spy on createReducer and simply verify that it was called with the expected INITIAL_STATE and mapping object.

The approach halfway between that and the full approach above is to mock the reducer functions, pass reducer various actions, and verify that the correct reducer function was called. This is probably the ideal approach but it is difficult to implement the way the code is currently written since createReducer runs as soon as the code is imported and captures references to the local functions setLanguage and getLanguage. If you wanted to use this approach then the easiest way to do it would be to move reducer to its own module so you could mock the reducer functions before importing reducer into your test code.

这篇关于如何对Reduxsauce进行单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆