React Native中如何测试基于useRef的Gorhom底部弹窗打开功能?
问题
我在React Native开发中,需要测试点击按钮时,基于Gorhom Bottom Sheet库开发的底部弹窗能否正常打开。这个弹窗通过useRef调用present方法实现打开操作,但遇到了两难问题:
- 不Mock
useRef上的方法时,测试报错:_addChannelBsRef$curr.present is not a function - 若Mock
useRef的方法,弹窗又无法实际打开,没法验证弹窗是否真的显示。
相关代码示例
触发打开的组件
const VerticalKlubListItemNewChannel = ({ openAddChannelBs, selectedKlub, }: VerticalKlubListItemNewChannelProps) => { const pressAddNewChannel = () => { openAddChannelBs(); }; return ( <TouchableOpacity testID={BtnTestIds.openNewChannelBtn} onPress={pressAddNewChannel} style={styles.container}> <CustomText color={colorsTheme.primary} fontWeight="semiBold"> new channel </CustomText> </TouchableOpacity> ) : null; };
传递给组件的打开函数
const openAddChannelBs = useCallback(() => { addChannelBsRef.current?.present(); }, []);
我的测试代码
describe("AddNewChannelBottomSheet", () => { it("open the bottom sheet onPress button", async () => { const { getByTestId } = render( <LeftDrawerChannel navigation={navigation as any} />, { preloadedState: { channel: { currentSelectedKlub: { user: { klubRole: "admin" } }, } as any, }, }, ); const { getByText } = render( <AddNewchannelBs addChannelBsRef={ mockOpenBsRef as unknown as BottomSheetModaleContainerRefType } />, ); const openBsAddNewchannel = getByTestId(BtnTestIds.openNewChannelBtn); expect(openBsAddNewchannel).toBeTruthy(); fireEvent.press(openBsAddNewchannel); const getText = getByText( "Enter the name of the channel that will be visible to members", ); await waitFor(() => { expect(getText).toBeTruthy(); }); }); });
解决方案
1. 正确模拟Bottom Sheet的Ref,兼顾方法跟踪与组件渲染
不要直接Mock useRef,而是创建一个模拟Ref对象,给它的current属性添加present方法,用jest.fn()跟踪调用状态,同时保证弹窗组件能正常渲染:
const mockAddChannelBsRef = { current: { present: jest.fn(), }, };
2. 把触发组件和弹窗放在同一个渲染上下文里
你之前两次调用render,导致触发组件和弹窗不在同一个React树中,点击按钮的事件无法关联到弹窗。必须将它们放在同一个render调用中,或者确保openAddChannelBs指向模拟Ref的present方法。调整后的测试代码如下:
describe("AddNewChannelBottomSheet", () => { it("opens the bottom sheet when button is pressed", async () => { const mockAddChannelBsRef = { current: { present: jest.fn(), }, }; const mockOpenAddChannelBs = () => { mockAddChannelBsRef.current?.present(); }; const { getByTestId, getByText } = render( <> <VerticalKlubListItemNewChannel openAddChannelBs={mockOpenAddChannelBs} selectedKlub={{ user: { klubRole: "admin" } }} /> <AddNewchannelBs addChannelBsRef={mockAddChannelBsRef as any} /> </>, { preloadedState: { channel: { currentSelectedKlub: { user: { klubRole: "admin" } }, } as any, }, }, ); const openBtn = getByTestId(BtnTestIds.openNewChannelBtn); expect(openBtn).toBeTruthy(); fireEvent.press(openBtn); // 先验证present方法被触发 expect(mockAddChannelBsRef.current.present).toHaveBeenCalled(); // 再验证弹窗内容是否显示 await waitFor(() => { expect(getByText("Enter the name of the channel that will be visible to members")).toBeTruthy(); }); }); });
3. 针对Gorhom库的特殊Mock(可选)
如果上述方法仍有问题,直接Mock Gorhom的BottomSheetModal组件,让它在测试环境下渲染内部内容,同时不影响方法跟踪:
// 在测试文件顶部添加Mock jest.mock('@gorhom/bottom-sheet', () => ({ ...jest.requireActual('@gorhom/bottom-sheet'), BottomSheetModal: ({ children }) => <>{children}</>, }));
这样弹窗内容会被正常渲染,你可以直接验证内容是否存在,同时结合Ref Mock确认present方法的调用。
4. 杜绝多次渲染
测试中只能调用一次render,多次渲染会生成独立的React树,状态和事件完全不互通,这是你之前测试失败的核心原因之一。
内容的提问来源于stack exchange,提问作者yoann84
相关产品推荐
相关产品推荐

