如何测试基于useQuery结果的条件渲染React组件?
测试基于TanStack Query的条件渲染组件
问题背景
组件通过useQuery异步获取分类数据,仅当categories存在时渲染Select组件。测试时无法找到combobox或option元素(报错:TestingLibraryElementError: Unable to find an accessible element with the role "option"),但未使用TanStack Query前测试逻辑正常。
组件核心代码
const { data: categories, isLoading } = useQuery<ICategoryItem[], boolean>({ queryKey: ['categoryItem'], queryFn: () => getCategoryItem(userId), enabled: userId !== '', }) return ( {categories && ( <div> <Select value={category.label} onValueChange={(value) => { const category = categories.filter( (category: ICategoryItem) => category._id === value )[0] setCategory(() => category) }} > {/* 选项渲染逻辑 */} </Select> </div> )} )
原测试代码(无效)
it('test', async () => { const user = userEvent.setup() const screen = render(<AtvtSection currentDate={new Date()} />) await user.click(screen.getByRole('combobox')) const comboboxList = screen.getAllByRole('option') await user.click(comboboxList[0]) // ... })
尝试过的无效模拟方式
function useCustomHook() { return useQuery({ queryKey: ['categoryItem'], queryFn: () => { return [ { _id: '66001e98d44dc9956110d48a', label: '💻', value: 'studying', userId: 'public', }, { _id: '6602463b6e2d7d198cba1c0f', label: '💪', value: 'exercise', userId: 'public', }, ] }, }) } const { result: categories } = renderHook(() => useCustomHook(), { wrapper }) console.log(categories) await waitFor(() => categories.current.isSuccess) expect(categories.current.data).toBeDefined()
解决方案
方法1:使用QueryClient预填充缓存(推荐)
利用TanStack Query的缓存机制,提前注入模拟数据,让组件直接读取缓存而非发起真实请求:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { userEvent, render, waitFor } from '@testing-library/react'; it('测试分类选择器交互', async () => { const user = userEvent.setup(); // 创建QueryClient实例,关闭重试避免测试等待 const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, }, }); // 预存`categoryItem`查询的模拟数据 queryClient.setQueryData(['categoryItem'], [ { _id: '66001e98d44dc9956110d48a', label: '💻', value: 'studying', userId: 'public', }, { _id: '6602463b6e2d7d198cba1c0f', label: '💪', value: 'exercise', userId: 'public', }, ]); // 用QueryClientProvider包裹组件,注入缓存数据 render( <QueryClientProvider client={queryClient}> <AtvtSection currentDate={new Date()} /> </QueryClientProvider> ); // 等待combobox渲染完成 await waitFor(() => { expect(screen.getByRole('combobox')).toBeInTheDocument(); }); await user.click(screen.getByRole('combobox')); // 用findAllByRole等待option加载(异步渲染) const options = await screen.findAllByRole('option'); await user.click(options[0]); // 后续断言逻辑,例如检查选中值是否正确 });
方法2:直接Mock useQuery钩子
用Jest直接mockuseQuery的返回值,强制组件使用模拟数据:
import { useQuery } from '@tanstack/react-query'; import { userEvent, render, waitFor } from '@testing-library/react'; // Mock整个useQuery模块 jest.mock('@tanstack/react-query'); it('测试分类选择器交互', async () => { const user = userEvent.setup(); // 设定useQuery的mock返回值 (useQuery as jest.Mock).mockReturnValue({ data: [ { _id: '66001e98d44dc9956110d48a', label: '💻', value: 'studying', userId: 'public', }, { _id: '6602463b6e2d7d198cba1c0f', label: '💪', value: 'exercise', userId: 'public', }, ], isLoading: false, isSuccess: true, }); render(<AtvtSection currentDate={new Date()} />); await waitFor(() => { expect(screen.getByRole('combobox')).toBeInTheDocument(); }); await user.click(screen.getByRole('combobox')); const options = await screen.findAllByRole('option'); await user.click(options[0]); // 后续断言逻辑 });
关键注意点
- 异步数据处理:
useQuery是异步操作,测试时必须用waitFor或findBy*系列查询方法,不能直接用getBy*(会因元素未渲染报错) - userId依赖:组件中
useQuery的enabled参数依赖userId,需确保测试时组件能获取到有效userId(如通过props或上下文传入) - 无效方法原因:之前自定义
useCustomHook并通过renderHook测试,仅验证了钩子本身,并未将模拟数据注入到AtvtSection组件中,组件仍会发起真实请求导致无数据。
内容的提问来源于stack exchange,提问作者kmyz
相关产品推荐
相关产品推荐

