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

使用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 }) }; });

实际执行顺序是:

  1. 运行jest.mock的工厂函数
  2. 声明并赋值mockPutObject

所以第一步执行时,mockPutObject还没被赋值,是undefined,导致S3实例的putObject就是undefined。

另外还要注意:你的测试函数里调用uploadFile是异步操作,必须用async/await等待它完成,否则断言会在putObject被调用之前执行,导致测试失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 10:52:29