【问题标题】:how to stub aws s3 upload with sinon如何使用 sinon 存根 aws s3 上传
【发布时间】:2021-09-26 14:00:59
【问题描述】:

如何在 Node.js 中存根 S3 上传?

我正在使用摩卡和诗乃。而且我有一个导出包含上传方法的类实例的文件。它看起来像这样:

// storage.ts
import * as AWS from 'aws-sdk';
import archiver from 'archiver';
import retry from 'bluebird-retry';


export class Storage {
  private readonly s3: AWS.S3 = new AWS.S3({
    endpoint: MINIO_ENDPOINT,
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    s3ForcePathStyle: true,
    signatureVersion: 'v4',
  });
  private readonly uploadBucket: string = UPLOAD_BUCKET;
  private readonly downloadBucket: string = DOWNLOAD_BUCKET;

  public async upload(localPath: string, s3Key: string, onProgress: (progress: number) => void): Promise<void> {
    await retry(async () => { // Be careful, it will influence stub.
      const stat = fse.statSync(localPath);
      let readable: stream.Readable;
      let archive: archiver.Archiver | undefined;
      if (stat.isFile()) {
        readable = fse.createReadStream(localPath);
      } else {
        archive = archiver('zip', { zlib: { level: 0 } }).directory(localPath, false);
        readable = archive;
      }
      const request = this.s3.upload({ Bucket: this.uploadBucket, Key: s3Key, Body: readable });
      request.on('httpUploadProgress', ({ loaded }) => {
        onProgress(loaded);
      });
      if (archive) {
        archive.finalize().catch(console.error);
      }

      await request.promise().catch((err) => {
        fse.removeSync(localPath);
        throw err;
      });
    }, { max_tries: UPLOAD_RETRY_TIMES, throw_original: true });
  }

}

export const storage = new Storage();

我尝试在我的单元测试中存根这个上传方法,它看起来像:

import { storage } from './storage';
import * as AWS from 'aws-sdk';
import sinon from 'sinon';

describe('Storage', () => {
  let sandbox: sinon.SinonSandbox;

  before(() => {
    sandbox = sinon.createSandbox();
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('upload', async () => {

    const s3Stub = sandbox.stub(AWS.S3.prototype, 'upload'); // something wrong

    await storage.upload(
      './package.json',
      's3Key',
      uploadBytes => { return uploadBytes; });

    expect(s3Stub).to.have.callCount(1);
    s3Stub.restore();

  });
});

我得到了一个错误:

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

我想测试一下上传方法,但是真的不要上传文件到s3。

我该怎么办?

谢谢大家。

【问题讨论】:

    标签: javascript node.js amazon-s3 mocha.js sinon


    【解决方案1】:

    在您的tsconfig.json 文件中启用esModuleInterop: true 配置并将import * as AWS from 'aws-sdk' 更改为import AWS from 'aws-sdk'

    由于new Storage() 语句将在您导入此模块时立即执行,因此您应该在存根AWS.S3 类之后导入./storage.ts 模块。

    附: 我删除了与问题无关的代码

    例如

    storage.ts:

    import AWS from 'aws-sdk';
    
    const UPLOAD_BUCKET = 'UPLOAD_BUCKET';
    const MINIO_ENDPOINT = 'MINIO_ENDPOINT';
    const AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID';
    const AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY';
    
    export class Storage {
      private readonly s3: AWS.S3 = new AWS.S3({
        endpoint: MINIO_ENDPOINT,
        accessKeyId: AWS_ACCESS_KEY_ID,
        secretAccessKey: AWS_SECRET_ACCESS_KEY,
        s3ForcePathStyle: true,
        signatureVersion: 'v4',
      });
      private readonly uploadBucket: string = UPLOAD_BUCKET;
    
      public async upload(localPath: string, s3Key: string, onProgress: (progress: number) => void): Promise<void> {
        const request = this.s3.upload({ Bucket: this.uploadBucket, Key: s3Key, Body: '123' });
      }
    }
    
    export const storage = new Storage();
    

    storage.test.ts:

    import AWS from 'aws-sdk';
    import sinon from 'sinon';
    
    describe('68431461', () => {
      let sandbox: sinon.SinonSandbox;
    
      before(() => {
        sandbox = sinon.createSandbox();
      });
    
      afterEach(() => {
        sandbox.restore();
      });
    
      it('should pass', async () => {
        const s3InstanceStub = { upload: sandbox.stub() };
        const s3Stub = sandbox.stub(AWS, 'S3').callsFake(() => s3InstanceStub);
        const { storage } = await import('./storage');
        const onProgressStub = sandbox.stub();
        await storage.upload('./package.json', 's3Key', onProgressStub);
        sinon.assert.calledOnce(s3Stub);
        sinon.assert.calledOnce(s3InstanceStub.upload);
      });
    });
    

    测试结果:

      68431461
        ✓ should pass (796ms)
    
    
      1 passing (810ms)
    
    ------------|---------|----------|---------|---------|-------------------
    File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ------------|---------|----------|---------|---------|-------------------
    All files   |     100 |      100 |     100 |     100 |                   
     storage.ts |     100 |      100 |     100 |     100 |                   
    ------------|---------|----------|---------|---------|-------------------
    

    【讨论】:

    • 感谢您的回答。我在我的计算机上运行您的测试代码,但收到错误“错误:超过 2000 毫秒的超时。对于异步测试和挂钩,请确保调用了“done()”;如果返回 Promise,请确保它已解决”。很奇怪。
    • @Rife 可能会受到其他有副作用的代码的影响。您没有为它们创建存根。因此,在测试执行过程中仍然使用它们原来的实现,可能会发起 HTTP 请求、RPC 调用等。您需要先定位问题,然后尝试 stub 他们。
    • 我找到了,有副作用的代码是retry(bluebird-retry),我在我的问题中去掉了。
    猜你喜欢
    • 2019-01-28
    • 2019-03-24
    • 2015-07-08
    • 2018-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-13
    相关资源
    最近更新 更多