测试调用 API 的 redux 操作

Posted

技术标签:

【中文标题】测试调用 API 的 redux 操作【英文标题】:Test redux actions that calls an API 【发布时间】: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;
  );
);

【问题讨论】:

【参考方案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())
  )
)

【讨论】:

另外请记住,createMockStore 已作为包发布:github.com/arnaudbenard/redux-mock-store 您可以使用github.com/wix/redux-testkit同步测试异步操作 嗨,如果正在测试的操作是getTodos 并且返回的数据是一个巨大的待办事项列表怎么办?你如何模拟这种情况?【参考方案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.

【讨论】:

以上是关于测试调用 API 的 redux 操作的主要内容,如果未能解决你的问题,请参考以下文章

React + Redux App:通过 Redux 操作调用 API Vs 直接调用 API

在 React Redux 中调度操作之前等待多个 Axios 调用

如何使用 redux-saga 处理异步 API 调用?

使用 Redux 让 React 组件执行一次性操作?

如何使用 Redux-Saga 和 Hooks 调用 API

Nock 在 Redux 测试中没有拦截 API 调用