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

如何用firebase-functions-test测试Firebase阻塞函数?遇wrap类型不兼容

测试Firebase阻塞函数:解决firebase-functions-test的wrap类型不兼容问题

我在用firebase-functions-test测试Firebase阻塞函数时,发现wrap函数不支持BlockingFunction类型,出现类型不匹配的报错。

生产代码

export const beforeSignIn = auth.user().beforeSignIn(async (user, context) => {
  if (user.phoneNumber == undefined) {
    throw new HttpsError(
        "permission-denied", "Invalid phone number");
  }
  return;
});

测试代码

const wrapped = wrap(beforeSignIn);

报错信息

No overload matches this call.
  Overload 1 of 3, '(cloudFunction: HttpsFunction & Runnable<unknown>): WrappedFunction<unknown, HttpsFunction & Runnable<unknown>>', gave the following error.
    Argument of type 'BlockingFunction' is not assignable to parameter of type 'HttpsFunction & Runnable<unknown>'.
      Property 'run' is missing in type 'BlockingFunction' but required in type 'Runnable<unknown>'.
  Overload 2 of 3, '(cloudFunction: CloudFunction<unknown>): WrappedScheduledFunction | WrappedFunction<unknown>', gave the following error.
    Argument of type 'BlockingFunction' is not assignable to parameter of type 'CloudFunction<unknown>'.
      Property 'run' is missing in type 'BlockingFunction' but required in type 'CloudFunction<unknown>'.
  Overload 3 of 3, '(cloudFunction: CloudFunction<CloudEvent<unknown>>): WrappedV2Function<CloudEvent<unknown>>', gave the following error.
    Argument of type 'BlockingFunction' is not assignable to parameter of type 'CloudFunction<CloudEvent<unknown>>'.
      Property 'run' is missing in type 'BlockingFunction' but required in type 'CloudFunction<CloudEvent<unknown>>'.

解决方案:直接调用核心业务逻辑

阻塞函数(如Auth的beforeSignIn前置触发器)本质是接收user和context参数的异步函数,无需通过wrap包装,直接抽离并调用核心逻辑即可:

步骤1:重构生产代码,抽离核心逻辑

把业务校验逻辑从触发器绑定中分离出来,单独作为可调用的函数:

import { auth, blockingFunctions } from 'firebase-functions/v2';
import { HttpsError } from 'firebase-functions/v2/https';

// 抽离独立的校验函数
export const validateUserPhoneNumber = async (user: auth.UserRecord, context: blockingFunctions.Context) => {
  if (user.phoneNumber == undefined) {
    throw new HttpsError("permission-denied", "Invalid phone number");
  }
  return;
};

// 绑定到阻塞触发器
export const beforeSignIn = auth.user().beforeSignIn(validateUserPhoneNumber);

步骤2:测试时直接调用抽离的函数

在测试代码中直接调用validateUserPhoneNumber,传入模拟的user和context对象即可:

import { validateUserPhoneNumber } from '../src/functions';
import { HttpsError } from 'firebase-functions/v2/https';
import { auth, blockingFunctions } from 'firebase-functions/v2';

test('无手机号用户登录时抛出权限错误', async () => {
  // 模拟无手机号的用户记录
  const mockUser = { phoneNumber: undefined } as auth.UserRecord;
  // 模拟触发器上下文
  const mockContext = {} as blockingFunctions.Context;

  await expect(validateUserPhoneNumber(mockUser, mockContext)).rejects.toThrow(HttpsError);
  await expect(validateUserPhoneNumber(mockUser, mockContext)).rejects.toHaveProperty('code', 'permission-denied');
});

test('有手机号用户登录时正常通过校验', async () => {
  const mockUser = { phoneNumber: '+1234567890' } as auth.UserRecord;
  const mockContext = {} as blockingFunctions.Context;

  await expect(validateUserPhoneNumber(mockUser, mockContext)).resolves.not.toThrow();
});

原因说明

firebase-functions-test的wrap方法仅适配HTTP函数、定时函数等常规CloudFunction类型,而阻塞函数(BlockingFunction)的内部结构没有run方法(这正是报错提示的核心问题),因此无法直接用wrap包装调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:33:37