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

如何使用Sinon模拟Azure Blob Storage中ContainerClient的listBlobsByHierarchy方法返回值?

如何用Sinon模拟Azure Blob Storage的ContainerClient.listBlobsByHierarchy方法

我来帮你搞定这个问题——模拟listBlobsByHierarchy确实有点特殊,因为它返回的是异步分页迭代器(PagedAsyncIterableIterator),不是普通数组。下面是一步步的具体实现方案,完全适配你的测试需求:

核心思路

要让Sinon正确Stub这个方法,关键是构造一个符合PagedAsyncIterableIterator接口的模拟对象,它需要支持for await...of遍历(这是你代码里用到的逻辑),同时满足Azure SDK的类型要求。


步骤1:构造模拟的Blob数据

先创建你需要的BlobItem和BlobPrefix实例,和真实返回的结构对齐:

// 模拟你的业务中需要的BlobItem
const mockBlobItems = [
  {
    kind: "blob",
    name: "docs/report.pdf",
    properties: { contentType: "application/pdf", lastModified: new Date("2024-01-01") },
    // 其他你代码中用到的属性都可以加上
  },
  {
    kind: "blob",
    name: "images/avatar.png",
    properties: { contentType: "image/png", lastModified: new Date("2024-02-15") },
  }
];

// 如果你的代码需要处理前缀(比如文件夹),可以模拟BlobPrefix
const mockBlobPrefixes = [
  {
    kind: "prefix",
    name: "archive/"
  }
];

步骤2:实现模拟的异步分页迭代器

我们需要构建一个对象,满足PagedAsyncIterableIterator的核心要求:支持异步迭代,能被for await...of遍历。这里写一个通用的构造函数:

function createMockPagedIterator(items) {
  let currentIndex = 0;

  return {
    // 实现迭代器的next方法,返回Promise
    async next() {
      if (currentIndex < items.length) {
        return { value: items[currentIndex++], done: false };
      }
      return { done: true };
    },
    // 可选:如果你的代码用到了分页(调用byPage()),可以补充这个方法
    byPage: async function* () {
      // 这里模拟单页返回所有数据,也可以根据需求拆分成分页
      yield items;
    },
    // 必须实现Symbol.asyncIterator,才能支持for await...of
    [Symbol.asyncIterator]() {
      return this;
    }
  };
}

// 合并BlobItem和BlobPrefix,生成完整的迭代器数据
const mockIteratorData = [...mockBlobItems, ...mockBlobPrefixes];
const mockPagedIterator = createMockPagedIterator(mockIteratorData);

步骤3:用Sinon Stub替换目标方法

根据你的代码结构,分两种常见情况处理:

情况1:ContainerClient通过依赖注入传入

如果你的API模块是通过参数接收ContainerClient实例(推荐的最佳实践),直接Stub实例的方法即可:

const sinon = require('sinon');
const { expect } = require('chai'); // 或你使用的断言库
const yourApiModule = require('../path-to-your-api-module');

describe('你的REST接口测试', () => {
  let containerClientStub;

  beforeEach(() => {
    // 创建ContainerClient的Stub对象,替换listBlobsByHierarchy方法
    containerClientStub = {
      listBlobsByHierarchy: sinon.stub().returns(mockPagedIterator)
    };
    // 注入Stub到你的API模块中
    yourApiModule.init(containerClientStub);
  });

  it('应该正确遍历Blob并转换格式', async () => {
    // 调用你的接口处理逻辑
    const transformedResult = await yourApiModule.processBlobs();

    // 验证方法被正确调用(比如传入了预期的分隔符和选项)
    expect(containerClientStub.listBlobsByHierarchy.calledWith('/', { /* 你的预期选项 */ })).to.be.true;

    // 验证转换后的结果符合预期
    expect(transformedResult).to.deep.equal([
      { fileName: 'report.pdf', type: 'application/pdf' },
      { fileName: 'avatar.png', type: 'image/png' }
    ]);
  });
});

情况2:直接导入@azure/storage-blob模块

如果你的代码是直接导入ContainerClient类,可以Stub它的原型方法:

const sinon = require('sinon');
const { ContainerClient } = require('@azure/storage-blob');
const yourApiModule = require('../path-to-your-api-module');

describe('你的REST接口测试', () => {
  let listBlobsStub;

  beforeEach(() => {
    // Stub ContainerClient原型上的listBlobsByHierarchy方法
    listBlobsStub = sinon.stub(ContainerClient.prototype, 'listBlobsByHierarchy').returns(mockPagedIterator);
  });

  afterEach(() => {
    // 测试结束后恢复原型,避免影响其他测试用例
    listBlobsStub.restore();
  });

  it('应该正确处理Blob数据', async () => {
    const result = await yourApiModule.processBlobs();

    // 验证方法被调用
    expect(listBlobsStub.called).to.be.true;
    // 验证结果正确性
    expect(result).to.deep.equal(/* 你的预期结果 */);
  });
});

额外注意事项

  • 如果你的代码用到了分页参数(比如maxPageSize),可以修改createMockPagedIterator的byPage方法,模拟多页返回的逻辑。
  • 确保模拟的BlobItem/BlobPrefix包含你代码中用到的所有属性,避免因为属性缺失导致测试失败。
  • 用Sinon的calledWith、callCount等断言方法,验证方法的调用参数和次数,确保你的业务逻辑符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:27:33