React Native中Jest+RN测试库模拟点击后useRef值变更失败求助
问题描述
我有一个名为MyComponent的组件,挂载时会调用两个API:初始显示第一个API的数据,点击指定按钮后切换显示第二个API的数据,使用useRef控制数据切换逻辑。组件代码如下:
const MyComponent = () => { const random = React.useRef(false); const { data, isFetching, isError } = useMakeFirstAPICall(); const { data: a, isFetching: b, isError: c, refetch: d } = useMakeSecondAPICall(); const usedData = random.current ? a : data; const usedIsFetching = random.current ? b : isFetching; const usedIsError = random.current ? c : isError; return ( <View> <Pressable testID="my.btn" onPress={() => { random.current = true; d(); }} > <MyCustomIcon /> </Pressable> {!usedIsFetching && <ShowData data={usedData} />} </View> ); }
测试时我已Mock API调用,但点击按钮后数据未更新,尝试Mock useRef也无效。我的测试代码如下:
jest.mock("src/api1", () => ({ useMakeFirstAPICall: () => ({ data: MockData1, }), })); jest.mock("src/api2", () => ({ useMakeSecondAPICall: () => ({ data: MockData2, }), })); describe("Testing MyComponent", () => { test("Test button press action", async () => { const reference = { current: true }; Object.defineProperty(reference, "current", { get: jest.fn(() => null), set: jest.fn(() => null), }); jest.spyOn(React, "useRef").mockReturnValueOnce(reference); render(<MyComponent />); const gridList1 = await screen.findAllByTestId("grid.card"); expect(gridList1).toBeTruthy(); expect(gridList1.length).toBe(5); const myButton = await screen.findByTestId("my.btn"); await fireEvent(myButton, "click"); const gridList2 = await waitFor(() => screen.findAllByTestId("grid.card")); expect(gridList2).toBeTruthy(); expect(gridList2.length).toBe(2); }); });
测试时报错(错误截图显示找不到grid.card元素),请问我哪里出错了?
问题原因与修复方案
1. 组件核心逻辑错误:useRef无法触发重渲染
useRef的更新不会触发React组件重渲染。你点击按钮时修改了random.current = true,但组件不会重新计算usedData、usedIsFetching这些变量,UI自然不会切换到第二个API的数据。
修复组件逻辑:
把useRef换成useState,因为useState的状态更新会触发组件重渲染:
const MyComponent = () => { // 用useState替代useRef,管理是否切换到第二个API const [useSecondAPI, setUseSecondAPI] = React.useState(false); const { data, isFetching, isError } = useMakeFirstAPICall(); const { data: secondData, isFetching: secondIsFetching, isError: secondIsError, refetch: refetchSecond } = useMakeSecondAPICall(); const usedData = useSecondAPI ? secondData : data; const usedIsFetching = useSecondAPI ? secondIsFetching : isFetching; const usedIsError = useSecondAPI ? secondIsError : isError; return ( <View> <Pressable testID="my.btn" onPress={() => { setUseSecondAPI(true); refetchSecond(); }} > <MyCustomIcon /> </Pressable> {!usedIsFetching && <ShowData data={usedData} />} </View> ); }
2. 测试代码中Mock useRef的致命错误
你Mock的useRef返回的current getter始终返回null,导致组件初始时usedData为null,根本不会渲染ShowData组件,所以初始断言就会找不到grid.card元素直接报错。而且就算组件逻辑正确,Mock useRef完全没必要。
修复测试代码:
移除对useRef的Mock,同时补充API Mock的完整字段(包括isFetching,避免逻辑异常):
jest.mock("src/api1", () => ({ useMakeFirstAPICall: () => ({ data: MockData1, isFetching: false, isError: false }), })); jest.mock("src/api2", () => ({ useMakeSecondAPICall: () => ({ data: MockData2, isFetching: false, isError: false, refetch: jest.fn() // Mock refetch函数 }), })); describe("Testing MyComponent", () => { test("Test button press action", async () => { render(<MyComponent />); // 验证初始显示第一个API的数据 const gridList1 = await screen.findAllByTestId("grid.card"); expect(gridList1.length).toBe(5); // 点击切换按钮 const myButton = screen.getByTestId("my.btn"); fireEvent.click(myButton); // 等待UI更新,验证第二个API的数据 await waitFor(() => { const gridList2 = screen.findAllByTestId("grid.card"); expect(gridList2.length).toBe(2); }); }); });
3. 额外注意事项
- 确保
MockData1包含5条数据、MockData2包含2条数据,和测试断言匹配。 - 测试中无需用
await获取按钮,组件挂载后按钮已存在,直接用screen.getByTestId即可。 - 必须Mock
refetch为jest函数,避免调用真实API逻辑。
内容的提问来源于stack exchange,提问作者Tushar Pramanik
相关产品推荐
相关产品推荐

