如何在 useEffect 中测试具有异步状态更新的钩子?

Pra*_*vin 2 testing reactjs react-testing-library react-hooks-testing-library

我有一个简单的钩子,可以获取值并将其设置为选项,如下所示:

import Fuse from 'fuse.js'
import React from 'react'

// prefetches options and uses fuzzy search to search on that option
// instead of fetching on each keystroke
export function usePrefetchedOptions<T extends {}>(fetcher: () => Promise<T[]>) {
  const [options, setOptions] = React.useState<T[]>([])
  React.useEffect(() => {
    // fetch options initially
    const optionsFetcher = async () => {
      try {
        const data = await fetcher()
        setOptions(data)
      } catch (err) {
        errorSnack(err)
      }
    }
    optionsFetcher()
  }, [])
  // const fuseOptions = {
  //   isCaseSensitive: false,
  //   keys: ['name'],
  // }

  // const fuse = new Fuse(options, fuseOptions)

  // const dataServiceProxy = (options) => (pattern: string) => {
  //   // console.error('options inside proxy call', { options })
  //   const optionsFromSearch = fuse.search(pattern).map((fuzzyResult) => fuzzyResult.item)
  //   return new Promise((resolve) => resolve(pattern === '' ? options : optionsFromSearch))
  // }

  return options
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用以下代码来测试它:

import { act, renderHook, waitFor } from '@testing-library/react-hooks'
import { Wrappers } from './test-utils'
import { usePrefetchedOptions } from './usePrefetchedOptions'
import React from 'react'

const setup = ({ fetcher }) => {
  const {
    result: { current },
    waitForNextUpdate,
    ...rest
  } = renderHook(() => usePrefetchedOptions(fetcher), { wrapper: Wrappers })
  return { current, waitForNextUpdate, ...rest }
}

describe('usePrefetchedOptions', () => {
  const mockOptions = [
    {
      value: 'value1',
      text: 'Value one',
    },
    {
      value: 'value2',
      text: 'Value two',
    },
    {
      value: 'value3',
      text: 'Value three',
    },
  ]
  test('searches for appropriate option', async () => {
    const fetcher = jest.fn(() => new Promise((resolve) => resolve(mockOptions)))
    const { rerender, current: options, waitForNextUpdate } = setup({ fetcher })
    await waitFor(() => {
      expect(fetcher).toHaveBeenCalled()
    })
    // async waitForNextUpdate()
    expect(options).toHaveLength(3) // returns initial value of empty options = []
  })
})
Run Code Online (Sandbox Code Playgroud)

问题是当我尝试在测试结束时断言选项时,它仍然具有初始值[]。但是,如果我将值记录在钩子内,它将返回模拟选项。在 useEffect 以异步方式更新钩子后,如何更新钩子。

waitForNextUpdate我也尝试过在代码中注释的地方使用 using 。它超时并出现以下错误: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:

小智 12

一些事情,目前您正在等待fetcher在测试中被调用,但状态更新实际上不是在fetcher调用之后发生,而是在fetcher返回的承诺得到解决之后发生。所以你需要在测试中等待该承诺的解决

result.current此外,您还破坏了首次渲染钩子时的值。该值只是第一次渲染后的副本result.current,之后不会更新。要查询 的当前值,您应该在断言中options查询。result.current

const fetcherPromise = Promise.resolve(mockOptions);
const fetch = jest.fn(() => fetcherPromise);
const { result } = renderHook(() => usePrefetchedOptions(fetcher), { wrappers: Wrappers })
await act(() => fetcherPromise);
expect(result.current).toHaveLength(3) 
Run Code Online (Sandbox Code Playgroud)