使用Jest模拟aws-sdk的S3模块失效问题求助
解决Jest模拟AWS S3时putObject为undefined的问题
这个问题的核心原因是Jest会把所有jest.mock调用提升到模块的最顶部执行,比你声明的mockPutObject变量更早。当jest.mock的工厂函数运行时,mockPutObject还没被初始化,自然就是undefined,导致S3实例的putObject方法不存在。
下面给你两种可行的解决方案,适配TypeScript场景:
方案一:使用Jest自动模拟+mockImplementation
这是最推荐的方式,TypeScript类型支持更友好,也方便管理mock:
import { uploadFile } from './uploadFile'; import AWS from 'aws-sdk'; // 自动模拟aws-sdk模块,Jest会生成一个mock版本替换原模块 jest.mock('aws-sdk'); // 类型断言:告诉TypeScript AWS.S3是一个Jest mock函数 const MockS3 = AWS.S3 as jest.Mock; const mockPutObject = jest.fn(() => ({ promise: jest.fn().mockResolvedValue({}) // 模拟promise成功返回 })); // 在每个测试前重置mock,避免测试间污染 beforeEach(() => { MockS3.mockClear(); mockPutObject.mockClear(); // 让S3构造函数返回带有mock putObject的实例 MockS3.mockImplementation(() => ({ putObject: mockPutObject })); }); describe('Test uploadFile', () => { it('should call putObject with correct parameters', async () => { // 必须await异步函数,否则测试会提前结束导致断言失败 await uploadFile('key', { test: 'test' }); // 验证S3构造函数被调用 expect(MockS3).toHaveBeenCalledTimes(1); // 验证putObject的调用参数正确 expect(mockPutObject).toHaveBeenCalledWith({ Bucket: 'myBucket', Key: 'key', Body: { test: 'test' } }); }); it('should handle errors when putObject fails', async () => { // 模拟promise失败 const mockError = new Error('Upload failed'); mockPutObject.mockReturnValue({ promise: jest.fn().mockRejectedValue(mockError) }); // 捕获console.log,验证错误被打印 const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); await uploadFile('key', { test: 'test' }); expect(consoleSpy).toHaveBeenCalledWith(mockError); consoleSpy.mockRestore(); }); });
方案二:在mock工厂函数内创建并暴露mock
如果你更习惯把mock定义在工厂函数里,可以通过给mock对象添加额外属性的方式暴露mock,方便测试时访问:
// 先定义mock,注意jest.mock会被提升,但这里工厂函数内部的mock是局部的 jest.mock('aws-sdk', () => { const mockPutObject = jest.fn(() => ({ promise: jest.fn().mockResolvedValue({}) })); return { S3: jest.fn(() => ({ putObject: mockPutObject })), // 额外暴露mock,方便测试文件访问 __testMocks: { putObject: mockPutObject } }; }); import { uploadFile } from './uploadFile'; import AWS from 'aws-sdk'; // 类型断言获取暴露的mock const mockPutObject = (AWS as any).__testMocks.putObject; describe('Test uploadFile', () => { beforeEach(() => { mockPutObject.mockClear(); }); it('should call putObject', async () => { await uploadFile('key', { test: 'test' }); expect(mockPutObject).toHaveBeenCalled(); }); });
为什么你原来的写法失效?
再仔细解释下:Jest的jest.mock调用会被提升到模块的最顶部,不管你写在代码的哪个位置。比如你原来的代码:
const mockPutObject = jest.fn(() => ({ promise: jest.fn() })); jest.mock('aws-sdk', () => { return { S3: () => ({ putObject: mockPutObject }) }; });
实际执行顺序是:
- 运行
jest.mock的工厂函数 - 声明并赋值
mockPutObject
所以第一步执行时,mockPutObject还没被赋值,是undefined,导致S3实例的putObject就是undefined。
另外还要注意:你的测试函数里调用uploadFile是异步操作,必须用async/await等待它完成,否则断言会在putObject被调用之前执行,导致测试失败。
内容的提问来源于stack exchange,提问作者Lycaon
相关产品推荐
相关产品推荐

