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

Jest无法Mock @PreProcessor中的requireAppCheck,执行真实代码报错

问题:Mock typescript-rest的@Preprocessor装饰器中的函数失败

项目代码结构

firebase.client.ts

import * as config from 'config';
import * as firebase from 'firebase-admin';

/**
 * Abstracts the initialization and direct use of Firebase.
 */
export class FirebaseClient {
    static initialized = false;

    static initialize() {
        if (!FirebaseClient.initialized) {
            const firebaseConfig: any = config.get('firebase');
            firebase.initializeApp({
                credential: firebase.credential.cert(JSON.parse(firebaseConfig.privateKey)),
                databaseURL: firebaseConfig.databaseUrl,
            });
            FirebaseClient.initialized = true;
        }
    }
}

auth-preprocessors.ts

import * as firebase from 'firebase-admin';
import { Errors } from 'typescript-rest';
import { logger } from './logger';
import { Request } from 'express';
import { FirebaseClient } from './firebase.client';

/**
 * @see https://firebase.google.com/docs/app-check/custom-resource-backend
 * @param message
 */
export async function requireAppCheck(req: Request) {
    FirebaseClient.initialize(); // Initialize FirebaseClient
    doSomeStuff();
}

authentication.controller.ts

import { Inject } from 'typescript-ioc';
import { PATCH, Path, Preprocessor } from 'typescript-rest';
import { requireAppCheck } from '../utils/auth-preprocessors';

@Path('/')
@Security('apiKey')
export class AuthenticationController {

    @Path('v1/path')
    @PATCH
    @Preprocessor(requireAppCheck)
    public async myFunc(
    ): Promise<Response> {
        return await doSomething();
    }
}

当前测试代码(authentication.controller.spec.ts)

import * as config from 'config';
import { Container } from 'typescript-ioc';
import * as AuthProcessors from '../utils/auth-preprocessors';
// 其余导入语句

describe.only('PATCH /path', () => {
    let requireAppCheckMock: jest.Mock;

    beforeEach(() => {
        requireAppCheckMock = jest.fn().mockResolvedValue('someValue');
        jest.spyOn(AuthProcessors, 'requireAppCheck').mockImplementation(requireAppCheckMock);
    });

    it.only('does stuff', async () => {
          const response = callController();
          expect(response.status).toBe(200); 
    })
})

问题现象

运行测试时触发500错误,错误栈显示FirebaseClient.initialize执行了真实逻辑(JSON解析失败),说明requireAppCheck未被正确Mock:

text: '{"status":500,"error":"Internal server error","stack":"SyntaxError: Unexpected end of JSON input\n at JSON.parse ()\n at Function.FirebaseClient.initialize (/path/to/firebase.client.ts:14:59)"}'

尝试过以下jest.mock写法,结果一致:

jest.mock('./../utils/auth-preprocessors', () => ({
    requireAppCheck: requireAppCheckMock
}))

核心原因

requireAppCheck作为@Preprocessor装饰器的参数使用时,装饰器在模块加载阶段就已经绑定了真实函数引用,后续的Mock无法覆盖这个提前绑定的引用。但如果在myFunc内部调用该函数,Mock能正常生效。

解决方案

方案1:提前Mock模块,确保装饰器加载时拿到Mock函数

在测试文件最顶部(导入controller之前)执行jest.mock,让装饰器在模块加载时就使用Mock函数:

// authentication.controller.spec.ts
// 先Mock auth-preprocessors模块,必须在导入controller之前执行
jest.mock('../utils/auth-preprocessors', () => ({
    requireAppCheck: jest.fn().mockResolvedValue('someValue')
}));

// 再导入所有依赖
import * as config from 'config';
import { Container } from 'typescript-ioc';
import * as AuthProcessors from '../utils/auth-preprocessors';
import { AuthenticationController } from './authentication.controller';
// 其余导入语句

describe.only('PATCH /path', () => {
    let requireAppCheckMock: jest.Mock;

    beforeEach(() => {
        // 获取Mock函数并重置状态
        requireAppCheckMock = AuthProcessors.requireAppCheck as jest.Mock;
        requireAppCheckMock.mockClear();
    });

    it.only('does stuff', async () => {
          const response = callController();
          expect(response.status).toBe(200);
          // 验证Mock函数被调用
          expect(requireAppCheckMock).toHaveBeenCalled();
    })
})

方案2:封装Preprocessor为类方法,通过依赖注入替换

把requireAppCheck改为类的实例方法,利用typescript-ioc的依赖注入来替换实现:

  1. 修改auth-preprocessors.ts:
import * as firebase from 'firebase-admin';
import { Errors } from 'typescript-rest';
import { logger } from './logger';
import { Request } from 'express';
import { FirebaseClient } from './firebase.client';

export class AuthPreprocessors {
    async requireAppCheck(req: Request) {
        FirebaseClient.initialize();
        doSomeStuff();
    }
}
  1. 修改controller:
import { Inject } from 'typescript-ioc';
import { PATCH, Path, Preprocessor } from 'typescript-rest';
import { AuthPreprocessors } from '../utils/auth-preprocessors';

@Path('/')
@Security('apiKey')
export class AuthenticationController {
    @Inject
    private authProcessors: AuthPreprocessors;

    @Path('v1/path')
    @PATCH
    @Preprocessor((req) => this.authProcessors.requireAppCheck(req))
    public async myFunc(
    ): Promise<Response> {
        return await doSomething();
    }
}
  1. 测试时替换依赖:
// authentication.controller.spec.ts
import * as config from 'config';
import { Container } from 'typescript-ioc';
import { AuthPreprocessors } from '../utils/auth-preprocessors';
import { AuthenticationController } from './authentication.controller';

describe.only('PATCH /path', () => {
    let mockAuthProcessors: jest.Mocked<AuthPreprocessors>;

    beforeEach(() => {
        // 创建Mock实例
        mockAuthProcessors = {
            requireAppCheck: jest.fn().mockResolvedValue('someValue')
        } as jest.Mocked<AuthPreprocessors>;
        
        // 替换容器中的实例
        Container.bind(AuthPreprocessors).toConstantValue(mockAuthProcessors);
    });

    it.only('does stuff', async () => {
          const response = callController();
          expect(response.status).toBe(200);
          expect(mockAuthProcessors.requireAppCheck).toHaveBeenCalled();
    })
})

方案3:直接Mock FirebaseClient.initialize

如果不想修改业务代码,可以跳过MockrequireAppCheck,直接MockFirebaseClient的静态方法,避免真实初始化逻辑执行:

// authentication.controller.spec.ts
import * as config from 'config';
import { Container } from 'typescript-ioc';
import * as AuthProcessors from '../utils/auth-preprocessors';
import { FirebaseClient } from '../utils/firebase.client';

// Mock FirebaseClient.initialize
jest.mock('../utils/firebase.client', () => ({
    FirebaseClient: {
        initialized: false,
        initialize: jest.fn()
    }
}));

describe.only('PATCH /path', () => {
    beforeEach(() => {
        (FirebaseClient.initialize as jest.Mock).mockClear();
    });

    it.only('does stuff', async () => {
          const response = callController();
          expect(response.status).toBe(200);
          // 可选:验证初始化方法被调用
          expect(FirebaseClient.initialize).toHaveBeenCalled();
    })
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:54:56