You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:27:28