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的依赖注入来替换实现:
- 修改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(); } }
- 修改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(); } }
- 测试时替换依赖:
// 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
相关产品推荐
相关产品推荐

