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

Jest测试使用Firebase Admin SDK访问Firestore超时问题及最佳实践

Jest中使用Firebase Admin SDK操作Firestore的实现方案

超时问题根因

代码存在3个直接触发线上连接超时的问题:

  • 客户端SDK与Admin SDK初始化逻辑混用,Admin SDK初始化时未显式传入projectId,线上环境无法自动匹配目标项目
  • 未显式区分模拟器/线上运行环境,切换线上时Admin SDK可能残留模拟器连接配置,或未正确加载服务账号权限范围
  • Admin SDK初始化后未等待凭据加载完成就直接调用admin.firestore(),首次鉴权握手失败后会持续重试直到触发超时
  • 注意:客户端SDK初始化用的web端firebaseConfig与Admin SDK是完全独立的两个实例,二者的db、auth实例不可混用

可直接复用的修正代码

把初始化逻辑按环境拆分,避免混写:

import { initializeApp as initializeClientApp, getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/app";
import * as admin from "firebase-admin";
import type { ServiceAccount } from "firebase-admin/app";
import type { HttpsCallable } from "firebase/functions";

// 全局实例声明
let issueRefundWix: HttpsCallable<unknown, unknown>;
let adminDb: admin.firestore.Firestore;
let uid: string;

const initFirebase = async () => {
  // 优先初始化Admin SDK,用于服务端数据操作
  const serAcc: ServiceAccount = await import(process.env.GOOGLE_APPLICATION_CREDENTIALS);
  const isEmulator = !!process.env.FIRESTORE_EMULATOR_HOST;
  
  if (!admin.apps.length) {
    admin.initializeApp({
      credential: admin.credential.cert(serAcc),
      projectId: serAcc.project_id, // 必须显式传入项目ID,避免自动探测失败
      storageBucket: "**.appspot.com", // 不要带gs://前缀
    });
  }
  adminDb = admin.firestore();
  // 模拟器环境下显式指定本地连接地址
  if (isEmulator) {
    adminDb.settings({
      host: process.env.FIRESTORE_EMULATOR_HOST,
      ssl: false
    });
  }

  // 初始化客户端SDK,用于模拟前端调用可调用函数
  const clientApp = initializeClientApp(firebaseConfig);
  const functions = getFunctions(clientApp, "us-central1");
  if (isEmulator) {
    connectFunctionsEmulator(functions, "127.0.0.1", 5001);
  }

  return { functions, adminDb };
};

beforeAll(async () => {
  // 给初始化、冷启动、首次鉴权留足超时时间
  jest.setTimeout(30000);
  const { functions } = await initFirebase();
  issueRefundWix = httpsCallable(functions, "issueRefundWix");
  // 先做连通性校验,避免测试执行到一半才发现连接失败
  await adminDb.collection("_connectivity_check").doc("test").get({ source: "server" });
  uid = await createWixUser();
  exampleSubscription.wixId = uid;
}, 30000);

// 测试结束后主动断开连接,避免Jest进程挂起
afterAll(async () => {
  await admin.app().delete();
});

Jest测试中使用Admin SDK的最佳实践

  • 严格区分SDK使用场景:客户端SDK(firebase/*包)用于模拟前端用户操作、调用可调用函数;Admin SDK用于测试前置数据准备、后端数据校验、测试后数据清理,二者初始化完全独立,不要互相传递实例
  • 显式区分运行环境:通过FIRESTORE_EMULATOR_HOST环境变量判断是否走模拟器,线上测试时提前清除所有模拟器相关环境变量,避免SDK错误连接本地端口
  • 初始化必须显式传入projectId:不要依赖SDK的自动项目探测,直接从导入的服务账号凭据中取project_id传入即可,能解决80%的连接超时问题
  • 增加前置连通性校验:beforeAll钩子中先执行一次强制走服务端的简单读操作,确认连接正常后再跑后续测试,避免每个用例都卡超时
  • 测试完成后主动销毁Admin App实例:避免SDK保持的长连接导致Jest测试跑完后进程无法正常退出
  • 线上测试使用独立测试项目:不要直接连生产环境Firestore,单独创建测试专用项目,测试前后用Admin SDK清理测试数据,避免污染线上资源
  • 合理设置超时阈值:线上调用Firestore存在网络开销,Jest默认5秒超时不足,给涉及Firestore操作的钩子和用例设置15-30秒超时即可,不需要开到10分钟

关于「能不能用和模拟器完全相同的方式测试已部署云函数」:测试用例的业务逻辑代码可以完全一致,仅初始化连接配置需要按环境切换,不需要修改用例本身的逻辑。只要初始化环节正确区分环境,模拟器跑通的用例可以直接对接线上已部署函数执行。

内容的提问来源于stack exchange,提问作者Matthew Keller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 21:24:21