测试调用API的redux动作

6
这个函数最好的测试方法是什么?
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;
  });
});
3个回答

12

来自Redux “编写测试” 食谱:

For async action creators using Redux Thunk or other middleware, it’s best to completely mock the Redux store for tests. You can still use applyMiddleware() with a mock store, as shown below (you can find the following code in redux-mock-store). You can also use nock to mock the HTTP requests.

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

can be tested like:

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
请注意,这个 createMockStore 已经作为一个包发布了:https://github.com/arnaudbenard/redux-mock-store - Dan Abramov
您可以使用 https://github.com/wix/redux-testkit 同步测试异步操作。 - yedidyak
你好,如果被测试的操作是 getTodos 并且返回的数据是一个巨大的待办事项列表,那么该如何模拟这种情况呢? - shangsunset

2

我会使用一个桩件 axios (例如通过使用mock-require),并编写一个测试,实际调用receivingItems()(dispatch, getState),并确保dispatch被正确调用。


2

我用了一种不同的方式来解决这个问题:将axios作为action的依赖注入。相比于“重连”依赖项,我更喜欢这种方法。

所以我采用了测试redux-connected组件的相同方法。当我导出actions时,我导出了两个版本:一个带有(用于组件),一个没有(用于测试)绑定依赖项。

这是我的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() // 这是绑定了axios的版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接