【问题标题】:Test redux actions that calls an API测试调用 API 的 redux 操作
【发布时间】:2016-02-21 01:10:16
【问题描述】:

测试此功能的最佳方法是什么

export function receivingItems() {
  return (dispatch, getState) => {
    axios.get('/api/items')
      .then(function(response) {
        dispatch(receivedItems(response.data));
      });
  };
}

这是我目前拥有的

describe('Items Action Creator', () => {
  it('should create a receiving items function', () => {
    expect(receivingItems()).to.be.a.function;
  });
});

【问题讨论】:

    标签: javascript unit-testing redux redux-thunk


    【解决方案1】:

    来自 Redux “Writing Tests” 配方:

    对于使用 Redux Thunk 或其他中间件的异步操作创建者,最好完全模拟 Redux 存储进行测试。您仍然可以将applyMiddleware() 与模拟商店一起使用,如下所示(您可以在redux-mock-store 中找到以下代码)。您还可以使用 nock 模拟 HTTP 请求。

    function fetchTodosRequest() {
      return {
        type: FETCH_TODOS_REQUEST
      }
    }
    
    function fetchTodosSuccess(body) {
      return {
        type: FETCH_TODOS_SUCCESS,
        body
      }
    }
    
    function fetchTodosFailure(ex) {
      return {
        type: FETCH_TODOS_FAILURE,
        ex
      }
    }
    
    export function fetchTodos() {
      return dispatch => {
        dispatch(fetchTodosRequest())
        return fetch('http://example.com/todos')
          .then(res => res.json())
          .then(json => dispatch(fetchTodosSuccess(json.body)))
          .catch(ex => dispatch(fetchTodosFailure(ex)))
      }
    }
    

    可以像这样测试:

    import expect from 'expect'
    import { applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    import * as actions from '../../actions/counter'
    import * as types from '../../constants/ActionTypes'
    import nock from 'nock'
    
    const middlewares = [ thunk ]
    
    /**
     * Creates a mock of Redux store with middleware.
     */
    function mockStore(getState, expectedActions, done) {
      if (!Array.isArray(expectedActions)) {
        throw new Error('expectedActions should be an array of expected actions.')
      }
      if (typeof done !== 'undefined' && typeof done !== 'function') {
        throw new Error('done should either be undefined or function.')
      }
    
      function mockStoreWithoutMiddleware() {
        return {
          getState() {
            return typeof getState === 'function' ?
              getState() :
              getState
          },
    
          dispatch(action) {
            const expectedAction = expectedActions.shift()
    
            try {
              expect(action).toEqual(expectedAction)
              if (done && !expectedActions.length) {
                done()
              }
              return action
            } catch (e) {
              done(e)
            }
          }
        }
      }
    
      const mockStoreWithMiddleware = applyMiddleware(
        ...middlewares
      )(mockStoreWithoutMiddleware)
    
      return mockStoreWithMiddleware()
    }
    
    describe('async actions', () => {
      afterEach(() => {
        nock.cleanAll()
      })
    
      it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
        nock('http://example.com/')
          .get('/todos')
          .reply(200, { todos: ['do something'] })
    
        const expectedActions = [
          { type: types.FETCH_TODOS_REQUEST },
          { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something']  } }
        ]
        const store = mockStore({ todos: [] }, expectedActions, done)
        store.dispatch(actions.fetchTodos())
      })
    })
    

    【讨论】:

    【解决方案2】:

    我会使用一个存根axios(例如使用mock-require)并编写一个实际调用receivingItems()(dispatch, getState)的测试,并确保使用正确的数据调用dispatch

    【讨论】:

      【解决方案3】:

      我以不同的方式解决了这个问题:将 axios 作为操作的依赖项注入。我更喜欢这种方法而不是“重新布线”依赖项。

      所以我使用了相同的方法来测试与 redux 连接的组件。当我导出操作时,我会导出两个版本:一个带有(用于组件)和一个不带(用于测试)绑定依赖项。

      这是我的 actions.js 文件的样子:

      import axios from 'axios'
      
      export const loadDataRequest = () => {
        return {
          type: 'LOAD_DATA_REQUEST'
        }
      }
      export const loadDataError = () => {
        return {
          type: 'LOAD_DATA_ERROR'
        }
      }
      export const loadDataSuccess = (data) =>{
        return {
          type: 'LOAD_DATA_SUCCESS',
          data
        }
      }
      export const loadData = (axios) => {
        return dispatch => {
          dispatch(loadDataRequest())
          axios
            .get('http://httpbin.org/ip')
            .then(({data})=> dispatch(loadDataSuccess(data)))
            .catch(()=> dispatch(loadDataError()))
        }
      }
      export default {
        loadData: loadData.bind(null, axios)
      }
      

      然后使用jest (actions.test.js) 进行测试:

      import { loadData } from './actions'
      
      describe('testing loadData', ()=>{
        test('loadData with success', (done)=>{
      
          const get = jest.fn()
          const data = {
            mydata: { test: 1 }
          }
          get.mockReturnValue(Promise.resolve({data}))
      
          let callNumber = 0
          const dispatch = jest.fn(params =>{
            if (callNumber===0){
              expect(params).toEqual({ type: 'LOAD_DATA_REQUEST' })
            }
            if (callNumber===1){
              expect(params).toEqual({
                type: 'LOAD_DATA_SUCCESS',
                data: data
              })
              done()
            }
            callNumber++
          })
          const axiosMock = {
            get
          }
          loadData(axiosMock)(dispatch)
        })
      })
      

      在组件中使用操作时,我会导入所有内容:

      import Actions from './actions'

      并发送:

      Actions.loadData() // this is the version with axios binded.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-21
        • 2017-05-22
        • 2017-05-04
        • 1970-01-01
        • 2019-08-30
        相关资源
        最近更新 更多