如何Mock CloudStorageAccount.CreateCloudBlobClient()编写单元测试?
如何Mock CloudStorageAccount.CreateCloudBlobClient()来编写单元测试
你的目标方法里直接硬编码创建CloudBlobClient,这种紧耦合的写法没法直接Mock静态的CloudStorageAccount.CreateCloudBlobClient()方法。要解决这个问题,核心思路是先重构代码解耦依赖,再针对解耦后的依赖进行Mock。
1. 重构代码解耦依赖
方式一:通过构造函数注入CloudBlobClient
把CloudBlobClient的创建逻辑移到类外部,通过构造函数注入进来,这样测试时就能传入Mock的实例:
// 修改类的构造函数,注入CloudBlobClient private readonly CloudBlobClient _cloudBlobClient; public YourClass(CloudBlobClient cloudBlobClient) { _cloudBlobClient = cloudBlobClient; } private async Task<BlobResultSegment> GetBlobResultSegment(string containerName, string prefix) { var cc = _cloudBlobClient.GetContainerReference(containerName); BlobResultSegment allBlobs = await cc.ListBlobsSegmentedAsync(prefix, default); return allBlobs; }
方式二:抽象Blob操作接口(更彻底的解耦)
如果想完全脱离对Azure Blob SDK具体类的依赖,可以抽象出一个Blob服务接口,让业务逻辑依赖接口而非具体实现:
// 定义接口 public interface IBlobAccessor { Task<BlobResultSegment> GetBlobResultSegment(string containerName, string prefix); } // 实现接口 public class BlobAccessor : IBlobAccessor { private readonly string _blobDataConnectionString; public BlobAccessor(string blobDataConnectionString) { _blobDataConnectionString = blobDataConnectionString; } public async Task<BlobResultSegment> GetBlobResultSegment(string containerName, string prefix) { CloudBlobClient cloudBlobClient = CloudStorageAccount.Parse(_blobDataConnectionString) .CreateCloudBlobClient(); var cc = cloudBlobClient.GetContainerReference(containerName); return await cc.ListBlobsSegmentedAsync(prefix, default); } }
之后业务类依赖IBlobAccessor,测试时直接Mock这个接口即可,完全不用管SDK的内部逻辑。
2. 编写单元测试(以Moq框架为例)
这里用方式一的重构代码做示例,展示如何Mock整个调用链:
[TestClass] public class YourClassTests { [TestMethod] public async Task GetBlobResultSegment_ReturnsExpectedBlobs() { // 1. 准备模拟的Blob数据和返回段 var mockBlobList = new List<IListBlobItem> { Mock.Of<CloudBlockBlob>(b => b.Name == "docs/file1.pdf"), Mock.Of<CloudBlockBlob>(b => b.Name == "docs/file2.pdf") }; var mockResultSegment = new BlobResultSegment(mockBlobList, null); // 2. Mock CloudBlobContainer,模拟ListBlobsSegmentedAsync的返回 var mockContainer = new Mock<CloudBlobContainer>(new Uri("https://test.blob.core.windows.net/test-container")); mockContainer.Setup(c => c.ListBlobsSegmentedAsync( It.IsAny<string>(), default, It.IsAny<BlobListingDetails>(), It.IsAny<int?>(), It.IsAny<BlobContinuationToken>(), It.IsAny<BlobRequestOptions>(), It.IsAny<OperationContext>(), It.IsAny<CancellationToken>())) .ReturnsAsync(mockResultSegment); // 3. Mock CloudBlobClient,模拟GetContainerReference的返回 var mockBlobClient = new Mock<CloudBlobClient>(new Uri("https://test.blob.core.windows.net/")); mockBlobClient.Setup(c => c.GetContainerReference(It.IsAny<string>())) .Returns(mockContainer.Object); // 4. 初始化被测类,传入Mock的CloudBlobClient var sut = new YourClass(mockBlobClient.Object); // 5. 执行测试方法 var result = await sut.GetBlobResultSegment("test-container", "docs/"); // 6. 验证结果 Assert.IsNotNull(result); Assert.AreEqual(2, result.Results.Count()); // 验证ListBlobsSegmentedAsync被正确调用 mockContainer.Verify(c => c.ListBlobsSegmentedAsync( "docs/", default, It.IsAny<BlobListingDetails>(), It.IsAny<int?>(), It.IsAny<BlobContinuationToken>(), It.IsAny<BlobRequestOptions>(), It.IsAny<OperationContext>(), It.IsAny<CancellationToken>()), Times.Once); } }
3. 不推荐直接Mock静态方法的原因
虽然可以用Microsoft Fakes、TypeMock这类工具Mock静态的CloudStorageAccount.Parse()或CreateCloudBlobClient(),但这种方式不建议:
- 静态Mock会让测试逻辑变得复杂,容易出现不稳定的情况
- 违背依赖倒置原则,代码的可维护性和扩展性都会变差
- 重构依赖注入的方式更符合面向对象设计,后续迭代也更省心
内容的提问来源于stack exchange,提问作者Juhi Shrivastava
相关产品推荐
相关产品推荐

