如何测试Firebase Auth onAuthStateChanged中的setState逻辑
问题根源
你当前的onAuthStateChanged mock实现直接返回空jest函数,组件调用该方法传入的回调既没有被保存,也不会被执行,因此无法触发组件内部的状态更新,只能覆盖初始状态下的测试场景。
改造Firebase Mock
核心思路是在mock层保存组件传入的auth状态回调,暴露给测试用例手动触发,同时对齐真实SDK的返回值(返回取消监听的函数),避免组件卸载报错:
// 存储onAuthStateChanged的回调引用,供测试用例手动触发 let authStateCallback: ((user: { email: string } | null) => void) | null = null; jest.mock('firebase/auth', () => ({ getAuth: () => ({}), // 仅作为入参传递,返回空对象即可 onAuthStateChanged: jest.fn((_authInstance, callback) => { authStateCallback = callback; // 返回和真实SDK一致的取消监听函数 return jest.fn(); }), })); // 每个用例执行前重置状态,避免用例间污染 beforeEach(() => { authStateCallback = null; jest.clearAllMocks(); });
测试用例实现
渲染目标组件后,手动调用保存的回调传入模拟用户数据,等待组件重渲染后断言路由渲染结果即可,覆盖两种核心场景:
- 已登录场景:回调传入带email字段的模拟用户对象,断言Dashboard路由渲染、未登录路由不展示
- 未登录场景:回调传入
null,断言Welcome、CreateAccount、Login路由渲染、Dashboard不展示
以React Testing Library为例,测试代码参考:
import { render, screen, waitFor } from '@testing-library/react-native'; import RootNavigator from './navigation/RootNavigator'; // 替换为你的路由组件路径 test('已登录状态渲染Dashboard页面', async () => { render(<RootNavigator />); // 触发已登录状态回调 expect(authStateCallback).toBeDefined(); authStateCallback({ email: 'test@example.com' }); await waitFor(() => { expect(screen.getByText('Dashboard')).toBeTruthy(); expect(screen.queryByText('Welcome')).toBeNull(); }); }); test('未登录状态渲染认证相关页面', async () => { render(<RootNavigator />); // 触发未登录状态回调 expect(authStateCallback).toBeDefined(); authStateCallback(null); await waitFor(() => { expect(screen.getByText('Welcome')).toBeTruthy(); expect(screen.getByText('CreateAccount')).toBeTruthy(); expect(screen.getByText('Login')).toBeTruthy(); expect(screen.queryByText('Dashboard')).toBeNull(); }); });
可选优化(非必须)
如果想进一步降低测试成本,可以把auth状态监听逻辑抽成独立自定义hook,把状态逻辑和UI渲染逻辑解耦:
// hooks/useAuthState.ts import { useEffect, useState } from 'react'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; export function useAuthState() { const [isSignedIn, setIsSignedIn] = useState<boolean>(false); const [displayName, setDisplayName] = useState<string | null>(''); useEffect(() => { const auth = getAuth(); const unsubscribe = onAuthStateChanged(auth, (user) => { if (user) { setDisplayName(user.email); setIsSignedIn(true); } else { setIsSignedIn(false); setDisplayName(''); } }); // 组件卸载时取消监听,避免内存泄漏 return unsubscribe; }, []); return { isSignedIn, displayName }; }
抽离后既可以单独给hook写单元测试,也可以在测试路由组件时直接mock这个hook的返回值,不用每次都走Firebase mock的触发逻辑。注意你原有代码里onAuthStateChanged直接写在组件函数体中,缺少useEffect包裹和取消监听的返回值,会导致每次组件重渲染都绑定新的监听,建议补上这部分逻辑。
内容的提问来源于stack exchange,提问作者bojackhorseman99
相关产品推荐
相关产品推荐

