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

如何用Sinon单元测试未导出私有功能?Mock AWS SES实现疑问

关于Sinon.js测试AWS.SES的问题解答

嘿,很高兴你开始接触单元测试和Sinon.js!先直接给你明确结论:在测试套件里自行声明sesConstructor确实属于反模式,而且是需要尽量避免的那种——因为你测试的不是实际业务代码里的那个函数,而是你复制出来的“替身”,这样的测试结果完全无法反映真实代码的运行情况,等于白忙活一场。

为什么这是反模式?

你复制的sesConstructor和原代码里的版本很容易出现差异:比如后续原代码更新了配置逻辑、调整了AWS参数,但你的测试里的版本没同步,就会出现“测试通过但实际代码出问题”的尴尬情况,彻底失去了单元测试的意义。

不用rewire的更好解决方案

下面给你几种更合理的方案,按推荐程度排序:

1. 重构原代码,导出sesConstructor

这是最推荐的做法,既提升了代码的可测试性,也符合模块化设计思路:

// ses.js
import AWS from 'aws-sdk';

let ses = {};
// 导出构造函数
export const sesConstructor = () => {
  AWS.config.update({ /* 你的配置内容 */ });
  ses = new AWS.SES({ apiVersion: '2010-12-01' });
};

// 导出实际使用的SES实例(或相关业务方法)
export const getSesInstance = () => ses;

测试时直接导入,用Sinon mock AWS的相关方法:

// 测试文件
import { sesConstructor, getSesInstance } from './ses.js';
import AWS from 'aws-sdk';
import sinon from 'sinon';

describe('SES初始化逻辑', () => {
  let awsMock;

  beforeEach(() => {
    // 创建AWS模块的mock
    awsMock = sinon.mock(AWS);
    // 预期config.update被调用一次,传入指定配置
    awsMock.expects('config.update').once().withArgs({ /* 你的预期配置 */ });
    // 模拟SES构造函数返回一个mock实例
    const mockSesInstance = { sendEmail: sinon.stub().resolves({}) };
    awsMock.expects('SES').once().returns(mockSesInstance);
  });

  afterEach(() => {
    // 恢复mock
    awsMock.restore();
  });

  it('应该正确初始化SES实例', () => {
    sesConstructor();
    // 验证所有mock调用都按预期执行
    awsMock.verify();
    expect(getSesInstance()).toBeDefined();
  });
});

2. 全局mock AWS模块(注意加载顺序)

如果暂时无法重构原代码,可以通过Sinon stub全局的AWS模块,但要确保先mock再导入ses.js(因为Node.js模块有缓存机制):

// 测试文件
import AWS from 'aws-sdk';
import sinon from 'sinon';
// 先mock AWS,再导入业务代码
import './ses.js';

describe('SES初始化逻辑', () => {
  let configStub, sesStub;

  beforeEach(() => {
    // Stub AWS.config.update方法
    configStub = sinon.stub(AWS.config, 'update');
    // Stub AWS.SES构造函数,返回mock实例
    const mockSesInstance = {};
    sesStub = sinon.stub(AWS, 'SES').returns(mockSesInstance);
  });

  afterEach(() => {
    // 恢复stub
    configStub.restore();
    sesStub.restore();
  });

  it('应该调用AWS配置和SES构造函数', () => {
    // 假设ses.js在模块加载时自动执行了sesConstructor
    expect(configStub.calledOnce).toBe(true);
    expect(sesStub.calledOnceWith({ apiVersion: '2010-12-01' })).toBe(true);
  });
});

3. 使用依赖注入降低耦合

把AWS作为参数传入sesConstructor,让代码耦合度更低,测试更灵活:

// ses.js
import AWS from 'aws-sdk';

let ses = {};
// 允许传入自定义的AWS实例,默认用全局的AWS
const sesConstructor = (awsInstance = AWS) => {
  awsInstance.config.update({ /* 你的配置 */ });
  ses = new awsInstance.SES({ apiVersion: '2010-12-01' });
};

// 业务方法也可以用同样的思路
export const sendEmail = (params, awsInstance = AWS) => {
  const sesInstance = new awsInstance.SES({ apiVersion: '2010-12-01' });
  return sesInstance.sendEmail(params).promise();
};

测试时直接传入mock的AWS对象,无需修改全局模块:

// 测试文件
import { sesConstructor } from './ses.js';
import sinon from 'sinon';

describe('SES初始化逻辑', () => {
  it('应该用mock AWS正确初始化', () => {
    // 创建mock的AWS对象
    const mockAWS = {
      config: { update: sinon.stub() },
      SES: sinon.stub().returns({})
    };

    sesConstructor(mockAWS);

    // 验证调用情况
    expect(mockAWS.config.update.calledOnce).toBe(true);
    expect(mockAWS.SES.calledOnceWith({ apiVersion: '2010-12-01' })).toBe(true);
  });
});

总结

尽量避免在测试里复制原函数的做法,优先选择重构导出函数或依赖注入的方案,这两种方式不仅能解决测试问题,还能优化代码结构。如果实在无法重构,再考虑全局mock的方式,但一定要注意模块加载顺序。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:39:59