如何用React Testing Library测试基于fixedCacheQuery的RTK Query组件B?
组件A
负责执行用户懒加载,代码如下:
const ComponentA = () => { const [trigger, {data}] = useLazyLoadUsers({ fixedCacheKey: fixedLoadUsersKey, }); useEffect(() => { trigger(); }, []); return <div>{data.map(user => user.id)}</div> }
组件B
需要在useLazyLoadUsers的isLoading为true时显示加载指示器,代码如下:
const ComponentB = () => { const [, {isLoading}] = useLazyLoadUsers({ fixedCacheKey: fixedLoadUsersKey, }); if (!isLoading) { return <div>Users loaded</div> } return <div>Loading users</div> }
问题描述
通过fixedCacheKey已实现两个组件的状态同步且运行正常,但找不到测试Component B的相关文档或示例。测试Component A的方法在Redux官方文档有说明。
我已有一个自定义的React Testing Library render方法,可提供包含所有自动生成查询的真实Store。希望通过模拟isLoading值来测试Component B是否渲染加载指示器,不想将isLoading状态复制到另一个slice中。
尝试过mock useLazyLoadUsers但未成功,也试过在渲染测试前dispatch初始化操作:
it('should render the loading indicator', async () => { const store = makeMockedStore(); store.dispatch(myApi.endpoints.loadUsers.initiate()); render(<ComponentB />, {store}); expect(await screen.findByText('Loading users')).toBeVisible(); })
但该方法无效,求解决思路或最佳实践。
解决思路与最佳实践
方案1:手动设置RTK Query缓存状态
既然使用真实Store,无需mock整个hook,可直接在测试前手动更新RTK Query的缓存状态,将对应查询的isLoading设为true。
RTK Query的状态存在state.api.queries下,针对你的fixedCacheKey,可直接修改Store初始状态:
it('should render the loading indicator', () => { const store = makeMockedStore({ api: { queries: { [fixedLoadUsersKey]: { isLoading: true, status: 'pending' // 补充必要的默认状态字段 } } } }); render(<ComponentB />, {store}); expect(screen.getByText('Loading users')).toBeVisible(); })
如果makeMockedStore不支持传入初始状态覆盖,可在dispatch后手动patch Store状态,或结合api.util.updateQueryData工具调整,但初始化时直接设置状态更高效。
方案2:正确处理异步状态更新
你之前的代码问题在于,dispatchinitiate()后,RTK Query的状态更新是异步的,直接渲染组件时状态可能尚未完成更新,导致断言失败。需要用waitFor等待组件状态同步:
it('should render the loading indicator', async () => { const store = makeMockedStore(); // 发起请求并保存promise,用于后续控制状态 const requestPromise = store.dispatch(myApi.endpoints.loadUsers.initiate()).unwrap(); render(<ComponentB />, {store}); // 等待组件感知到loading状态并更新渲染 await waitFor(() => { expect(screen.getByText('Loading users')).toBeVisible(); }); // 若需测试加载完成状态,可等待请求promise resolve await requestPromise; expect(screen.getByText('Users loaded')).toBeVisible(); })
注意要确保makeMockedStore正确配置了RTK Query中间件,且mock的API请求处于pending状态(比如用jest.useFakeTimers()延迟响应),否则请求会立即完成,isLoading仅瞬间为true,导致断言失败。
方案3:正确Mock useLazyLoadUsers Hook
之前mock失败大概率是路径或返回值结构错误。需确保mock的是正确的导入路径,且返回符合hook结构的数组:
// 假设useLazyLoadUsers从'../api'导入 jest.mock('../api', () => ({ ...jest.requireActual('../api'), // 保留其他未mock的导出 useLazyLoadUsers: jest.fn(() => [ jest.fn(), // mock触发函数 { isLoading: true, data: null } ]) })); it('should render the loading indicator', () => { render(<ComponentB />); expect(screen.getByText('Loading users')).toBeVisible(); }); // 测试加载完成状态 it('should render loaded state when not loading', () => { require('../api').useLazyLoadUsers.mockReturnValue([ jest.fn(), { isLoading: false, data: [{ id: 1 }] } ]); render(<ComponentB />); expect(screen.getByText('Users loaded')).toBeVisible(); });
这种方式适合单元测试,无需依赖Store,直接测试组件是否根据hook返回的isLoading值正确渲染内容。
最佳实践总结
- 集成测试(验证组件与Store/RTK Query的交互):优先用方案1或2,直接操作Store状态或发起真实查询请求,确保组件与状态管理的联动逻辑正确。
- 单元测试(仅关注组件自身渲染逻辑):用方案3,mock掉hook并控制输入,测试组件输出是否符合预期。
- 避免复制状态到其他slice,RTK Query的缓存状态可直接复用,测试时直接操作该状态即可。
内容的提问来源于stack exchange,提问作者ewulff

