在Apollo Server 4+Jest+TS中Mock ESM函数未生效问题排查
问题:Jest Mock AuthService方法未生效,Resolver仍调用原方法
我正在用Jest和TypeScript搭建Apollo Server 4的集成测试,Resolver代码中调用AuthService类的checkIfAuthorized方法做权限校验,想要Mock该方法使其固定返回指定值,但测试未生效——Resolver执行时还是会调用原方法。
相关代码
测试文件 src/tests/integration/integration.spec.ts
import {ApolloServer} from "@apollo/server"; import {readFileSync} from 'fs'; const typeDefs = readFileSync('./schema.graphql', {encoding: 'utf-8'}); import {resolvers} from "../../resolvers/resolvers.js"; import {jest} from "@jest/globals"; import {AuthService} from "../../utils/authorization"; jest.mock('../../utils/authorization'); describe('Your test suite description', () => { let authorization: AuthService; beforeEach(async () => { console.log("In before each ") authorization = new AuthService( // 缺少闭合括号,语法错误 }); it('returns hello with the provided name', async () => { const checkIfAuthorizedMock = jest.spyOn(authorization, 'checkIfAuthorized'); checkIfAuthorizedMock.mockResolvedValue(await Promise.resolve()); const testServer = new ApolloServer({ typeDefs, resolvers, }); const response = await testServer.executeOperation({ query: ` mutation addUser($userId: String!) { addUser( .... } `, variables: { userId: "1234", }, operationName: 'AddUser' },); console.log("Response " + JSON.stringify(response)) }); });
src/utils/authorization.ts
export class AuthService { async checkIfAuthorized(context?: MyContext, userId?: string) { // 原权限校验逻辑 } }
src/resolvers/resolver.ts
export const addUserResolver = async (_parent: any, args: { input: AddUser }, _context: MyContext ): Promise<User> => { try { await authorization.checkIfAuthorized(_context, args.input.userId) return await some_func() } catch (err) { // 错误处理 } }
tsconfig.json
{ "compilerOptions": { "rootDirs": ["src"], "outDir": "dist", "lib": ["es2020"], "target": "es2020", "strict": true, "module": "esnext", "noErrorTruncation": true, "preserveConstEnums": true, "noUnusedLocals": true, "noUnusedParameters": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "types": ["node", "jest"], }, "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" } }
jest-config
import type { JestConfigWithTsJest } from 'ts-jest' const jestConfig: JestConfigWithTsJest = { extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, transform: { '^.+\\.tsx?$': [ 'ts-jest', { useESM: true, }, ], }, } export default jestConfig
错误原因及解决方法
1. 导入顺序错误
jest.mock必须在导入依赖模块(比如resolvers)之前执行,否则resolvers模块加载时会使用未被Mock的原始AuthService类。
修正测试文件导入顺序:
import { jest } from "@jest/globals"; // 先执行mock,再导入其他依赖 jest.mock('../../utils/authorization'); import { ApolloServer } from "@apollo/server"; import { readFileSync } from 'fs'; const typeDefs = readFileSync('./schema.graphql', { encoding: 'utf-8' }); import { resolvers } from "../../resolvers/resolvers.js"; import { AuthService } from "../../utils/authorization";
2. Mock对象不匹配
你在测试中Mock的是自己创建的authorization实例,但Resolver中使用的是另一个独立实例(大概率是在resolvers模块初始化时创建的),所以Mock不会生效。需要直接Mock AuthService类的原型方法,覆盖所有实例的调用。
修正测试用例中的Mock逻辑:
it('returns hello with the provided name', async () => { // Mock类的原型方法,所有实例都会使用这个Mock const mockCheck = jest.spyOn(AuthService.prototype, 'checkIfAuthorized'); // 因为原方法是async,返回void的话直接传undefined即可 mockCheck.mockResolvedValue(undefined); const testServer = new ApolloServer({ typeDefs, resolvers, }); const response = await testServer.executeOperation({ query: ` mutation addUser($userId: String!) { addUser(userId: $userId) { id name } } `, variables: { userId: "1234", }, operationName: 'AddUser' }); console.log("Response ", JSON.stringify(response)); // 验证Mock方法确实被调用 expect(mockCheck).toHaveBeenCalledWith(expect.any(Object), "1234"); });
3. 语法错误
beforeEach中new AuthService(缺少闭合括号,修正为:
beforeEach(async () => { console.log("In before each ") authorization = new AuthService(); });
额外注意事项
如果Resolver中的authorization是模块级别的单例(比如在resolvers文件顶部直接const authorization = new AuthService()),必须确保jest.mock在导入resolvers之前执行,这样单例创建时使用的是Mock后的类,才能让Mock生效。
内容的提问来源于stack exchange,提问作者salt-pepper
相关产品推荐
相关产品推荐

