Mock的useNavigation触发但Jest的toHaveBeenCalledWith未识别问题
问题描述
我Mock了@react-navigation/native中的useNavigation()钩子,但Jest始终报错称该函数未被调用,可日志显示函数明明已经触发。尝试用waitFor包裹断言时,还出现TypeScript错误:Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'catch')]。
测试代码
jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useNavigation: () => ({ // 换成() => { console.log('trigger') }时能看到触发日志 navigate: jest.fn(), }), useRoute: () => ({ params: { email: '' }, }), }; }); .... it.only('should navigate to resend screen when button is pressed', async () => { const { getByTestId } = render( withStoreProvider(<EmailVerificationPending />), ); await waitFor(() => { expect( getByTestId('emailVerificationPendingScreen_moreInfoCta'), ).toBeTruthy(); }); fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta')); expect(navigation.navigate).toHaveBeenCalledWith( RootScreens.SignUpNavigator, { screen: SignUpScreens.EmailVerificationResend, params: { email: '', }, }, ); });
报错信息
● features/signup/presentation/email-verification/pending-screen › should navigate to resend screen when button is pressed expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: "RootScreens_SignUpNavigator", {"params": {"email": ""}, "screen": "SignUpScreens_EmailVerificationResend"} Number of calls: 0 64 | fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta')); 65 | > 66 | expect(navigation.navigate).toHaveBeenCalledWith(
解决方法
1. 确保Mock的navigate函数引用一致
当前Mock的useNavigation每次调用都会返回新的jest.fn()实例,导致测试代码中引用的navigation.navigate和组件实际调用的不是同一个函数。需要提前定义mock函数,让每次调用useNavigation都返回同一个对象:
// 提前定义mock函数,确保全局引用一致 const mockNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useNavigation: () => ({ navigate: mockNavigate, }), useRoute: () => ({ params: { email: '' }, }), }; });
然后在测试中直接使用这个提前定义的mock函数进行断言:
it.only('should navigate to resend screen when button is pressed', async () => { const { getByTestId } = render( withStoreProvider(<EmailVerificationPending />), ); // 等待元素渲染完成,建议用toBeInTheDocument替代toBeTruthy await waitFor(() => { expect(getByTestId('emailVerificationPendingScreen_moreInfoCta')).toBeInTheDocument(); }); fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta')); // 直接使用全局的mockNavigate断言 expect(mockNavigate).toHaveBeenCalledWith( RootScreens.SignUpNavigator, { screen: SignUpScreens.EmailVerificationResend, params: { email: '', }, }, ); });
2. 重置Mock避免测试污染
在每个测试前重置mock函数,防止其他测试的调用记录影响当前测试:
beforeEach(() => { mockNavigate.mockClear(); });
3. 解决waitFor的catch错误
该错误通常是因为组件内部存在未被正确Mock的异步操作(比如API请求、Redux异步Action),导致出现未处理的Promise rejection。需要Mock这些异步逻辑,让它们立即resolve:
- 如果组件中有调用Redux异步Action,需要Mock对应的thunk/saga,确保它们不会发起真实请求,而是直接返回成功结果。
- 如果有API调用,需要Mock对应的请求函数,让它返回resolved的Promise。
4. 替换断言方法
建议使用toBeInTheDocument替代toBeTruthy,这是@testing-library/jest-dom提供的更精准的DOM元素存在断言,需要确保已导入该库的匹配器(通常在setupTests.js中配置)。
内容的提问来源于stack exchange,提问作者Antoni
相关产品推荐
相关产品推荐

