You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用React-Testing-Library测试含内部AJAX钩子的组件?解决Jest加载异常

问题:RTL测试内部AJAX钩子组件时loading状态始终无法更新

我用React-Testing-Library(RTL)测试包含内部AJAX钩子的组件,当前Jest运行在旧版jsdom环境,测试时发现组件的loading状态一直为true,tableState从未更新。我不想mock内部的tableState/tableDispatch,因为强行传入mock会导致数据与视图分离,对懒加载和props传递产生反效果,希望避免提升该钩子。

自定义Hook代码

const useTableDataBackend = (
  dataInit,
  RequestHandler = (OnSuccess, onError) => {},
  MutationHandler = (data, timezone) => {}
) => {        
  const [tableState, tableDispatch] = useReducer(dataInit)
            
  const { headers, filters, rowsPerPage, currentPage } = tableState
            
  const getPageRequestHandler = (resetPage = true) => {
    tableDispatch({ type: 'SET_LOADING', payload: true })
            
    RequestHandler(
      (data) => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
      },
      () => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
      }
    )

    useEffect(() => {
      tableDispatch({ type: 'SET_DATA', payload: dataInit })
      tableDispatch({ type: 'SET_DATA_PAGINATED', payload: dataInit })
    }, [dataInit])

    useEffect(() => {
      getPageRequestHandler(false)
    }, [currentPage])
  }
}

组件使用示例

const myComponent = () => {
  const RequestHandler = () => {
    // Implementation here
  }

  const MutationHandler = () => {
    // Implementation here
  }

  const { tableState, tableDispatch } = useTableDataBackend(
    MutationHandler(RequestHandler()),
    RequestHandler,
    MutationHandler
  )

  return (
    <div>{tableState}</div>
  )
}

测试用例代码

jest.mock(
  'RequestHandler',
  () => jest.fn()
)

it('should paginate correctly onClick of each control', () => {
  RequestHandler.mockImplementationOnce(
    (OnSuccess, onError) => {
      //const data = Array.from({ length: 5 }, () => EncountersGetRequestHandlerSampleData.items.data[0])
      onSuccess(data)
    }
  )

  render(<myComponent />)
  // Expect loading to stop but never does, can't test a disabled paginator
})

解决方案

1. 修复自定义Hook的Hook规则违规问题

React要求Hook必须在组件或自定义Hook的顶层调用,不能嵌套在函数(比如getPageRequestHandler)内部。原代码中将useEffect放在getPageRequestHandler里,导致effect无法正常触发,状态更新逻辑失效。

修改后的useTableDataBackend:

const useTableDataBackend = (
  dataInit,
  RequestHandler = (OnSuccess, onError) => {},
  MutationHandler = (data, timezone) => {}
) => {        
  const [tableState, tableDispatch] = useReducer(dataInit)
            
  const { headers, filters, rowsPerPage, currentPage } = tableState
            
  const getPageRequestHandler = (resetPage = true) => {
    tableDispatch({ type: 'SET_LOADING', payload: true })
            
    RequestHandler(
      (data) => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
        // 补充:这里需要处理数据更新,比如设置表格数据
        tableDispatch({ type: 'SET_DATA', payload: data })
      },
      () => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
      }
    )
  }

  // 将useEffect移到Hook顶层
  useEffect(() => {
    tableDispatch({ type: 'SET_DATA', payload: dataInit })
    tableDispatch({ type: 'SET_DATA_PAGINATED', payload: dataInit })
  }, [dataInit])

  useEffect(() => {
    getPageRequestHandler(false)
  }, [currentPage])

  // 必须返回状态和dispatch,否则组件无法获取
  return { tableState, tableDispatch }
}

2. 测试中处理异步状态更新

React状态更新是异步的,测试时需要用RTL的waitFor等待状态变化完成,否则断言会在状态更新前执行:

import { render, waitFor } from '@testing-library/react'
import myComponent from './myComponent'
import RequestHandler from './RequestHandler'

jest.mock('./RequestHandler', () => jest.fn())

it('should paginate correctly onClick of each control', async () => {
  const mockData = { /* 替换为你的测试数据结构 */ }
  RequestHandler.mockImplementation((onSuccess) => {
    onSuccess(mockData)
  })

  const { queryByText, getByRole } = render(<myComponent />)

  // 等待loading状态消失
  await waitFor(() => {
    expect(queryByText('加载中')).not.toBeInTheDocument()
  })

  // 测试分页器是否可用(假设分页器role为"button")
  const paginationButtons = getByRole('button', { name: /分页/i })
  expect(paginationButtons).not.toBeDisabled()
})

3. 修正组件调用Hook的参数错误

原组件中useTableDataBackend的第一个参数是MutationHandler(RequestHandler()),这里直接调用了MutationHandler,传入的是它的返回值而非初始状态。需要确认dataInit的类型是否符合useReducer的初始值要求,比如应该传入初始状态对象,而非函数调用结果:

// 修正示例:假设dataInit是初始状态对象
const initialTableState = {
  loading: false,
  data: [],
  currentPage: 1,
  // 其他初始状态字段
}

const { tableState, tableDispatch } = useTableDataBackend(
  initialTableState,
  RequestHandler,
  MutationHandler
)

内容的提问来源于stack exchange,提问作者gunslingor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:54:56