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

Mock Azure BlobClient的DownloadToAsync参数异常问题求助

问题:Mock Azure BlobClient的DownloadToAsync方法时出现类型不匹配错误

场景

我要为Azure Storage的GetFileContentAsync方法编写单元测试,StorageService代码如下:

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Microsoft.Extensions.Options;

public class StorageService : IStorageService
{
 private const string DefaultContentType = "application/octet-stream";

private readonly BlobServiceClient _blobServiceClient;
private readonly StorageOptions _options;

public StorageService(BlobServiceClient blobServiceClient, IOptions<StorageOptions> storageOptions)
{
    _blobServiceClient = blobServiceClient;
    _options = storageOptions.Value;
}

public async Task<(byte[] Content, string ContentType)> GetFileContentAsync(string containerName, string filenamePath)
{
    using var memoryStream = new MemoryStream();

    var contentType = "application/octet-stream";

BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
bool exists = await containerClient.ExistsAsync();

    if (exists)
    {
        await foreach (var blobItem in containerClient.GetBlobsAsync(prefix: filenamePath))
        {
            var blobClient = containerClient.GetBlobClient(blobItem.Name);

            contentType = blobItem.Properties.ContentType;

            await blobClient.DownloadToAsync(memoryStream);
        }
    }

    return (memoryStream.ToArray(), contentType);
}
}

我的单元测试代码如下:

[Fact]
public async Task GetFileContentAsync_ReturnContent()
{
    // Arrange
    var mockBlobServiceClient = new Mock<BlobServiceClient>();
    var mockBlobContainerClient = new Mock<BlobContainerClient>();
    var mockBlobClient = new Mock<BlobClient>();
    var options = Options.Create(new StorageOptions());

    var containerName = "test-container";
    var filenamePath = "file/path/test.txt";

    var expectedContentType = "text/plain";
    var expectedContent = Encoding.UTF8.GetBytes("test content");

    var blobItems = new[]
    {
        BlobsModelFactory.BlobItem(filenamePath)
    };

    Page<BlobItem> page = Page<BlobItem>.FromValues(blobItems, null, Mock.Of<Response>());
    AsyncPageable<BlobItem> pageableBlobList = AsyncPageable<BlobItem>.FromPages(new[] { page });

    mockBlobServiceClient
        .Setup(x => x.GetBlobContainerClient(It.IsAny<string>()))
        .Returns(mockBlobContainerClient.Object);

    mockBlobContainerClient
        .Setup(client => client.ExistsAsync(CancellationToken.None))
        .ReturnsAsync(Response.FromValue(true, new Mock<Response>().Object));

    mockBlobContainerClient.Setup(c => c.GetBlobsAsync(It.IsAny<BlobTraits>(), It.IsAny<BlobStates>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
        .Returns(pageableBlobList);

    mockBlobContainerClient.Setup(x => x.GetBlobClient(It.IsAny<string>()))
        .Returns(mockBlobClient.Object);

    var memoryStream = new MemoryStream(expectedContent);

    mockBlobClient
    .Setup(x => x.DownloadToAsync(It.IsAny<Stream>(), CancellationToken.None))
    .Returns((Stream stm, CancellationToken token) => memoryStream.CopyToAsync(stm, token));

    var storageService = new StorageService(mockBlobServiceClient.Object, Options.Create(new StorageOptions()));

    // Act
    var (content, contentType) = await storageService.GetFileContentAsync(containerName, filenamePath);

    // Assert
    Assert.Equal(expectedContent, content);
    Assert.Equal(expectedContentType, contentType);
}

运行测试时触发错误:

System.ArgumentException : Invalid callback. Setup on method with return type 'Task<Response>' cannot invoke callback with return type 'Task'

错误原因

BlobClient.DownloadToAsync方法的返回类型是Task<Response>,但mock时返回的是CopyToAsync的结果(类型为Task),两者类型不匹配,导致参数异常。

解决方法

需要在mock中返回Task<Response>类型的结果,同时完成流的复制逻辑,另外还要修正BlobItem的ContentType设置,否则断言会失败:

修改后的完整测试代码

[Fact]
public async Task GetFileContentAsync_ReturnContent()
{
    // Arrange
    var mockBlobServiceClient = new Mock<BlobServiceClient>();
    var mockBlobContainerClient = new Mock<BlobContainerClient>();
    var mockBlobClient = new Mock<BlobClient>();
    var options = Options.Create(new StorageOptions());

    var containerName = "test-container";
    var filenamePath = "file/path/test.txt";

    var expectedContentType = "text/plain";
    var expectedContent = Encoding.UTF8.GetBytes("test content");

    // 修正BlobItem,设置正确的ContentType
    var blobItems = new[]
    {
        BlobsModelFactory.BlobItem(
            name: filenamePath,
            properties: BlobsModelFactory.BlobItemProperties(contentType: expectedContentType)
        )
    };

    Page<BlobItem> page = Page<BlobItem>.FromValues(blobItems, null, Mock.Of<Response>());
    AsyncPageable<BlobItem> pageableBlobList = AsyncPageable<BlobItem>.FromPages(new[] { page });

    mockBlobServiceClient
        .Setup(x => x.GetBlobContainerClient(It.IsAny<string>()))
        .Returns(mockBlobContainerClient.Object);

    mockBlobContainerClient
        .Setup(client => client.ExistsAsync(CancellationToken.None))
        .ReturnsAsync(Response.FromValue(true, Mock.Of<Response>()));

    mockBlobContainerClient.Setup(c => c.GetBlobsAsync(It.IsAny<BlobTraits>(), It.IsAny<BlobStates>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
        .Returns(pageableBlobList);

    mockBlobContainerClient.Setup(x => x.GetBlobClient(It.IsAny<string>()))
        .Returns(mockBlobClient.Object);

    // 修正DownloadToAsync的mock,匹配原方法返回类型
    mockBlobClient
        .Setup(x => x.DownloadToAsync(It.IsAny<Stream>(), CancellationToken.None))
        .Returns(async (Stream stm, CancellationToken token) => 
        {
            // 将预期内容写入传入的流
            await stm.WriteAsync(expectedContent, 0, expectedContent.Length, token);
            // 重置流位置,确保后续读取正常
            stm.Position = 0;
            // 返回空Response对象,匹配方法返回类型
            return Response.FromValue(true, Mock.Of<Response>());
        });

    var storageService = new StorageService(mockBlobServiceClient.Object, options);

    // Act
    var (content, contentType) = await storageService.GetFileContentAsync(containerName, filenamePath);

    // Assert
    Assert.Equal(expectedContent, content);
    Assert.Equal(expectedContentType, contentType);
}

关键说明

  1. 确保mock的返回类型与原方法一致:DownloadToAsync返回Task<Response>,必须返回包装了Response的Task。
  2. 直接将预期内容写入测试方法传入的流中,无需额外创建MemoryStream复制,逻辑更简洁。
  3. 通过BlobsModelFactory设置BlobItem的ContentType,保证断言能匹配预期值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 17:59:49