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

Sinon stub getSignedUrl报属性不可配置不可写入错误解决方法

问题原因

@aws-sdk/s3-request-presigner 包导出的getSignedUrl属性默认设置了writable: false、configurable: false的属性描述符,sinon默认的打桩逻辑无法直接改写这类不可写、不可配置的对象属性,因此抛出TypeError: Descriptor for property getSignedUrl is non-configurable and non-writable错误。

以下是三种Node.js 16 + JavaScript环境下可直接落地的解决方案:


解决方案

方案1:手动修改属性描述符后打桩(无额外依赖)

不需要安装额外工具,打桩前先手动改写属性的可配置、可写权限,测试结束后还原原属性即可,代码示例:

const s3RequestSigner = require("@aws-sdk/s3-request-presigner");
const expect = require('chai').expect;
const sinon = require('sinon');

it('should throw an error when getSignedUrl rejects', async function() {
  const sandbox = sinon.createSandbox();
  // 缓存原方法和原属性描述符,用于测试后还原
  const originalDescriptor = Object.getOwnPropertyDescriptor(s3RequestSigner, 'getSignedUrl');
  // 临时修改属性为可写、可配置
  Object.defineProperty(s3RequestSigner, 'getSignedUrl', {
    writable: true,
    configurable: true,
    value: s3RequestSigner.getSignedUrl
  });

  try {
    // 此时可以正常创建stub
    sandbox.stub(s3RequestSigner, "getSignedUrl").rejects(new Error("fakeUrl"));
    
    // 此处写入你的测试逻辑、断言代码
    // ...

  } finally {
    // 还原原属性描述符,避免污染其他测试用例
    Object.defineProperty(s3RequestSigner, 'getSignedUrl', originalDescriptor);
    sandbox.restore();
  }
})

注意必须把还原逻辑放在finally块中,即使测试断言失败也能正常还原环境。

方案2:使用模块替换工具做依赖mock(稳定性最高)

如果不想手动操作属性描述符,可以用模块级mock工具在加载业务代码时直接替换掉对应包的导出,从根源上避免修改原模块属性。
以常用的proxyquire为例,先安装开发依赖:
npm install -D proxyquire
测试代码写法:

const expect = require('chai').expect;
const sinon = require('sinon');
const proxyquire = require('proxyquire');

it('should throw an error when getSignedUrl rejects', async function() {
  // 提前创建stub
  const getSignedUrlStub = sinon.stub().rejects(new Error("fakeUrl"));
  // 加载业务代码时,替换掉@aws-sdk/s3-request-presigner的导出
  const businessModule = proxyquire('../你的业务代码路径.js', {
    '@aws-sdk/s3-request-presigner': {
      getSignedUrl: getSignedUrlStub
      // 如果业务代码还用到了这个包的其他导出方法,需要在这里一并补充,否则会被覆盖为undefined
    }
  });

  // 执行业务方法、写断言
  // ...
})

这种方式不需要操作原模块的属性,不会出现权限相关的报错,适合复杂项目的单元测试。

方案3:依赖注入优化(长期可维护性最好)

如果可以调整业务代码结构,最推荐的方式是把getSignedUrl作为依赖传入业务逻辑,测试时直接传入stub即可,完全不需要mock模块:
业务代码调整示例:

// 业务文件
async function generateS3PresignedUrl(s3Client, getSignedUrl) {
  // 内部逻辑直接使用传入的getSignedUrl,不要在文件顶部硬编码require
  const url = await getSignedUrl(s3Client, /* 你的命令参数 */);
  return url;
}

module.exports = { generateS3PresignedUrl };

测试代码可以写的非常简洁:

const expect = require('chai').expect;
const sinon = require('sinon');
const { generateS3PresignedUrl } = require('../你的业务代码路径.js');

it('should throw an error when getSignedUrl rejects', async function() {
  const mockGetSignedUrl = sinon.stub().rejects(new Error("fakeUrl"));
  try {
    await generateS3PresignedUrl({}, mockGetSignedUrl);
    expect.fail('预期抛出错误但未抛出');
  } catch (e) {
    expect(e.message).to.equal('fakeUrl');
    expect(mockGetSignedUrl.calledOnce).to.be.true;
  }
})

这种方式不需要任何mock工具,测试逻辑最清晰,不会出现模块mock带来的各种环境问题,唯一的成本是需要调整现有业务代码的写法。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 19:57:27