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); }
关键说明
- 确保mock的返回类型与原方法一致:
DownloadToAsync返回Task<Response>,必须返回包装了Response的Task。 - 直接将预期内容写入测试方法传入的流中,无需额外创建MemoryStream复制,逻辑更简洁。
- 通过
BlobsModelFactory设置BlobItem的ContentType,保证断言能匹配预期值。
内容的提问来源于stack exchange,提问作者Faouzeya
相关产品推荐
相关产品推荐

