在Jest中模拟TypeScript命名导出的单例类
解决Jest模拟GraphQL代理单例方法失效的问题
我完全懂你现在的挫败感——花了好几个小时调试,结果测试还是跑了真实的请求逻辑。问题出在两个核心点上:mock的时机不对,以及模拟的结构没匹配上单例导出的实际情况。
问题根源拆解
jest.mock执行时机太晚:你把jest.mock放在了beforeAll里,但测试文件顶部的import graphql from './graphql'已经提前加载了被测试模块,而这个模块早已经导入了真实的graphQlProxyService实例。此时再去mock模块,根本替换不掉已经存在的真实实例。模拟结构不匹配:你当前的mock试图让
graphQlProxyService变成一个构造函数(jest.fn().mockImplementation(...)),但实际上你导出的是一个已经实例化完成的对象,不是类本身。这种模拟方式和真实导出的结构完全对不上,自然不会生效。
可行解决方案
方案1:提前mock整个模块(推荐)
把jest.mock移到测试文件最顶部,在导入任何依赖之前执行,这样mock会直接覆盖模块的导出。然后在测试用例里自定义request方法的行为:
import { createMocks } from 'node-mocks-http' // 先mock模块,再导入被测试代码 jest.mock('@services/graphql-proxy.service', () => ({ graphQlProxyService: { request: jest.fn() // 先创建空的mock函数占位 } })) import graphql from './graphql' import { graphQlProxyService } from '@services/graphql-proxy.service' describe('when there is an error with the request', () => { it('returns status code 500', async () => { // 给mock的request方法指定错误返回(注意异步方法要用mockRejectedValue) (graphQlProxyService.request as jest.Mock).mockRejectedValue(new Error('Mock request error')) const { req, res } = createMocks({ method: 'POST', body: {}, }) await graphql(req, res) expect(res._getStatusCode()).toBe(500) }) })
方案2:用jest.spyOn监听实例方法
如果你不想提前mock整个模块,也可以直接用jest.spyOn替换单例实例上的request方法:
import { createMocks } from 'node-mocks-http' import graphql from './graphql' import { graphQlProxyService } from '@services/graphql-proxy.service' describe('when there is an error with the request', () => { let requestSpy: jest.SpyInstance beforeEach(() => { // 监听request方法,替换为mock实现 requestSpy = jest.spyOn(graphQlProxyService, 'request') .mockRejectedValue(new Error('Mock request error')) }) afterEach(() => { // 测试后恢复原方法,避免影响其他测试用例 requestSpy.mockRestore() }) it('returns status code 500', async () => { const { req, res } = createMocks({ method: 'POST', body: {}, }) await graphql(req, res) expect(res._getStatusCode()).toBe(500) }) })
关键细节提醒
- 因为
request是异步方法,必须用mockRejectedValue模拟错误,不能直接throw new Error()——前者返回rejected的Promise,和真实异步方法抛错的行为一致;后者是同步抛错,会导致测试逻辑和真实代码不符。 - 记住Jest模块mock的核心规则:
jest.mock必须放在所有导入语句之前,才能正确覆盖模块导出。
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

