如何用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
相关产品推荐
相关产品推荐

