redux 如何用Jest测试复杂的异步化简器

mwyxok5s  于 2022-11-12  发布在  Jest
关注(0)|答案(1)|浏览(142)

我有这样的reducer,使用fetch API作为它的基础最终:

export const fetchRelatedFamilies = () => {
  return (dispatch, getState) => {
    if (isEmpty(getState().relatedFamiliesById)) {
      dispatch({ type: REQUEST_RELATED_FAMILIES_BY_ID })
      new HttpRequestHelper('/api/related_families',
        (responseJson) => {
          dispatch({ type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: responseJson.relatedFamiliesById })
        },  
        e => dispatch({ type: RECEIVE_RELATED_FAMILIES_BY_ID, error: e.message, updates: {} }), 
      ).get()
    }   
  }
}

HttpRequestHelper的程式码如下:https://github.com/broadinstitute/seqr/blob/master/ui/shared/utils/httpRequestHelper.js
下面是我如何尝试测试它(但它不工作):

import configureStore from 'redux-mock-store'
import fetchMock from 'fetch-mock'
import thunk from 'redux-thunk'
import { cloneDeep } from 'lodash'
import { fetchRelatedFamilies, REQUEST_RELATED_FAMILIES_BY_ID, RECEIVE_RELATED_FAMILIES_BY_ID } from 'redux/rootReducer'

import { STATE1 } from '/shared/components/panel/fixtures.js'

describe('fetchRelatedFamilies', () => {
  const middlewares = [thunk]
  const testActionsDispatch = async (currstate, expectedActions) => {
    const store = configureStore(middlewares)(currstate)
    store.dispatch(fetchRelatedFamilies())

    // need to mimick wait for async actions to be dispatched
    //await new Promise((r) => setTimeout(r, 200));
    expect(store.getActions()).toEqual(expectedActions)
  }

  afterEach(() => {
    fetchMock.reset()
    fetchMock.restore()
  })  

  it('Dispatches correct actions when data - relatedFamiliesById - is absent in state', () => {
    const relatedFamiliesById = cloneDeep(STATE1.relatedFamiliesById)
    fetchMock
      .getOnce('/api/related_families', { body: relatedFamiliesById, headers: { 'content-type': 'application/json' } })

    STATE1.relatedFamiliesById = {}
    const expectedActions = [ 
      { type: REQUEST_RELATED_FAMILIES_BY_ID },
      { type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: relatedFamiliesById }
    ]   
    testActionsDispatch(STATE1, expectedActions)
  })  
})

我在结果存储操作中没有看到{ type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: relatedFamiliesById },所以我尝试使用以下技巧:await new Promise((r) => setTimeout(r, 200));,希望这是异步fetch的问题,但它导致的是,无论预期的操作是什么,测试都会通过,就好像await后面的代码被完全忽略了一样。我不能使用store.dispatch(fetchRelatedFamilies()).then(...,可能是因为没有返回Promise,并且我得到了then access of undefined错误。我尝试从库中使用waitForhttps://testing-library.com/docs/guide-disappearance/,但是由于项目本身的性质和版本,我在安装库本身时遇到了很大的麻烦,所以我仍然需要以某种方式避免它。
因此,我唯一的问题是如何使异步reducer内部的操作出现,在本例中为{ type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: relatedFamiliesById }

q35jwt9p

q35jwt9p1#

当前代码的问题是,尽管您在testActionsDispatch帮助器方法中等待了200ms(以便解决模拟的承诺),但您在测试代码中并没有等待解决该200ms的承诺。
为了做到这一点,你必须声明你的测试是异步的,并等待testActionsDispatch代码的执行:

const testActionsDispatch = async (currstate, expectedActions) => {
    const store = configureStore(middlewares)(currstate)
    store.dispatch(fetchRelatedFamilies())

    // need to mimick wait for async actions to be dispatched
    await new Promise((r) => setTimeout(r, 200));
    expect(store.getActions()).toEqual(expectedActions)
}

// Note that the test is declared as async
it('Dispatches correct actions when data - relatedFamiliesById - is absent in state', async () => {
    const relatedFamiliesById = cloneDeep(STATE1.relatedFamiliesById)
    fetchMock
      .getOnce('/api/related_families', { body: relatedFamiliesById, headers: { 'content-type': 'application/json' } })

    STATE1.relatedFamiliesById = {}
    const expectedActions = [ 
      { type: REQUEST_RELATED_FAMILIES_BY_ID },
      { type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: relatedFamiliesById }
    ]

    // Await the execution of the helper code  
    await testActionsDispatch(STATE1, expectedActions)
})

现在应该可以了,但是我们在使用这个testActionsDispatch助手的每个测试中增加了200ms的延迟。这可能会在启动测试时增加很多时间,最终在逻辑层面上并不能真正确保承诺得到解决。
一个更好的方法是在reducer中返回promise,这样我们就可以等待它在测试中直接解析(我假设HttpRequestHelper中的get方法返回通过fetch创建的promise并返回它):

export const fetchRelatedFamilies = () => {
  return (dispatch, getState) => {
    if (isEmpty(getState().relatedFamiliesById)) {
      dispatch({ type: REQUEST_RELATED_FAMILIES_BY_ID })
      return new HttpRequestHelper('/api/related_families',
        (responseJson) => {
          dispatch({ type: RECEIVE_RELATED_FAMILIES_BY_ID, updates: responseJson.relatedFamiliesById })
        },  
        e => dispatch({ type: RECEIVE_RELATED_FAMILIES_BY_ID, error: e.message, updates: {} }), 
      ).get()
    }   
  }
}

然后,在你的助手中,你可以简单地等待这个返回的承诺来解决:

const testActionsDispatch = async (currstate, expectedActions) => {
    const store = configureStore(middlewares)(currstate)
    // Await for the promise instead of awaiting a random amount of time.
    await store.dispatch(fetchRelatedFamilies())

    expect(store.getActions()).toEqual(expectedActions)
}

相关问题